diff --git a/.all-contributorsrc b/.all-contributorsrc index 66f56d8e..f0018ca9 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -3,19 +3,15 @@ "projectOwner": "adaures", "repoType": "gitlab", "repoHost": "https://code.castopod.org", - "files": [ - "README.md", - "docs/src/index.md" - ], + "files": ["README.md"], "imageSize": 100, "commit": false, - "contributorsPerLine": 7, "contributors": [ { "login": "yassinedoghri", "name": "Yassine Doghri", - "avatar_url": "https://code.castopod.org/uploads/-/system/user/avatar/3/avatar.png", - "profile": "https://github.com/yassinedoghri", + "avatar_url": "https://avatars.githubusercontent.com/u/11021441?v=4", + "profile": "https://yassinedoghri.com", "contributions": [ "code", "bug", @@ -100,24 +96,14 @@ "name": "Lyonel Bernard", "avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg", "profile": "https://twitter.com/lyonelbernard", - "contributions": [ - "bug", - "question", - "audio", - "ideas" - ] + "contributions": ["bug", "question", "audio", "ideas"] }, { "login": "ctlw83", "name": "Christopher Lagonick-Weitzel", "avatar_url": "https://secure.gravatar.com/avatar/7c2a721b52d0763673a600e8f01bd745?s=80&d=identicon", "profile": "https://www.crypticchameleon.com/", - "contributions": [ - "bug", - "question", - "audio", - "ideas" - ] + "contributions": ["bug", "question", "audio", "ideas"] }, { "login": "ernestoacostame", @@ -135,24 +121,33 @@ "ideas" ] }, + { + "login": "3wen", + "name": "Ewen", + "avatar_url": "https://mastodon.fedi.bzh/system/accounts/avatars/000/000/002/original/6f387690a504ae46.jpg", + "profile": "https://mastodon.fedi.bzh/@ewen", + "contributions": [ + { + "type": "translation", + "url": "https://translate.castopod.org" + }, + "ideas", + "code" + ] + }, { "login": "Behel", "name": "Bastien Luneteau", "avatar_url": "https://secure.gravatar.com/avatar/ad63ee8ef8e3db8253d21e5012d2724f?s=80&d=identicon", "profile": "https://code.castopod.org/Behel", - "contributions": [ - "code", - "bug" - ] + "contributions": ["code", "bug"] }, { "login": "cecillie", "name": "Cécile Ricordeau", "avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg", "profile": "https://www.cecillie.fr/", - "contributions": [ - "design" - ] + "contributions": ["design"] }, { "login": "PatrykMis", @@ -171,48 +166,35 @@ "name": "Marcin Lewandowski", "avatar_url": "https://secure.gravatar.com/avatar/eed8337939641eac5ad0b570bd6acf96?s=80&d=identicon", "profile": "https://code.castopod.org/mspanc", - "contributions": [ - "bug", - "ideas" - ] + "contributions": ["bug", "ideas"] }, { "login": "SJanik", "name": "Sebastian Janik", "avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg", "profile": "https://code.castopod.org/SJanik", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "patryk", "name": "Patryk Karczmarczyk", "avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg", "profile": "https://code.castopod.org/patryk", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "ddenis", "name": "denis d", "avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg", "profile": "https://code.castopod.org/ddenis", - "contributions": [ - "bug", - "ideas" - ] + "contributions": ["bug", "ideas"] }, { "login": "douglaskastle", "name": "Douglas Kastle", "avatar_url": "https://secure.gravatar.com/avatar/b7e652ba4b6bcd440afa069e7f7bc9e6?s=80&d=identicon", "profile": "https://code.castopod.org/douglaskastle", - "contributions": [ - "bug", - "ideas" - ] + "contributions": ["bug", "ideas"] }, { "login": "cExplorer", @@ -232,66 +214,49 @@ "name": "ImaCrea", "avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg", "profile": "https://code.castopod.org/imacrea", - "contributions": [ - "bug", - "ideas" - ] + "contributions": ["bug", "ideas"] }, { "login": "jonas", "name": "Jonas S", "avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg", "profile": "https://code.castopod.org/jonas", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "yannL", "name": "LEFEBVRE Yann", "avatar_url": "https://secure.gravatar.com/avatar/9c46600ce566ec6d526370d8e104b1c8?s=80&d=identicon", "profile": "https://code.castopod.org/yannL", - "contributions": [ - "bug" - ] + "contributions": ["bug"] }, { "login": "spaetz", "name": "Sebastian Späth", "avatar_url": "https://secure.gravatar.com/avatar/278e1af65e82993efd0ba7bbbacf6435?s=80&d=identicon", "profile": "https://code.castopod.org/spaetz", - "contributions": [ - "bug", - "ideas" - ] + "contributions": ["bug", "ideas"] }, { "login": "rocky", "name": "rocky III", "avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg", "profile": "https://code.castopod.org/rocky", - "contributions": [ - "bug" - ] + "contributions": ["bug"] }, { "login": "Regenpfeifer", "name": "Hermann Josef Eckl", "avatar_url": "https://code.castopod.org/uploads/-/system/user/avatar/103/avatar.png", "profile": "https://code.castopod.org/Regenpfeifer", - "contributions": [ - "bug" - ] + "contributions": ["bug"] }, { "login": "cyrilledel", "name": "Delhaye Cyrille", "avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg", "profile": "https://code.castopod.org/cyrilledel", - "contributions": [ - "bug", - "ideas" - ] + "contributions": ["bug", "ideas"] }, { "login": "otetranome", @@ -330,19 +295,6 @@ } ] }, - { - "login": "3wen", - "name": "Ewen", - "avatar_url": "https://mastodon.fedi.bzh/system/accounts/avatars/000/000/002/original/6f387690a504ae46.jpg", - "profile": "https://mastodon.fedi.bzh/@ewen", - "contributions": [ - { - "type": "translation", - "url": "https://translate.castopod.org" - }, - "ideas" - ] - }, { "login": "forght", "name": "forght", @@ -370,7 +322,7 @@ { "login": "BoFFire", "name": "ButterflyOfFire", - "avatar_url": "https://static.mstdn.fr/static/accounts/avatars/000/065/901/original/e18d44b28edd0ada.png", + "avatar_url": "https://static.mstdn.fr/static/accounts/avatars/000/065/901/original/5908e93ad5447f15.png", "profile": "https://mstdn.fr/@ButterflyOfFire", "contributions": [ { @@ -492,14 +444,12 @@ "name": "Dimitri Regnier", "avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg", "profile": "https://dimitriregnier.net/", - "contributions": [ - "ideas" - ] + "contributions": ["ideas"] }, { "login": "irithys", "name": "irithys", - "avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/15405614/large/e46d7f8e9f7c05997827563c3a3cf942.jpeg", + "avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/15405614/large/3086461c47cce0a0c031925e5f943412.png", "profile": "https://im.irithys.com/@thy", "contributions": [ { @@ -521,16 +471,104 @@ ] }, { - "login": "ghose", - "name": "ghose (XoseM)", - "avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/12617257/large/a201650da44fed28890b0e0d8477a663.jpg", - "profile": "https://crowdin.com/profile/xosem", + "login": "basen1982", + "name": "Andreas Olsson", + "avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg", + "profile": "https://crowdin.com/profile/basen1982", "contributions": [ { "type": "translation", "url": "https://translate.castopod.org" } ] + }, + { + "login": "leonfrom", + "name": "leonfrom", + "avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg", + "profile": "https://crowdin.com/profile/leonfrom", + "contributions": [ + { + "type": "translation", + "url": "https://translate.castopod.org" + } + ] + }, + { + "login": "agentcobra57", + "name": "agentcobra", + "avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg", + "profile": "https://crowdin.com/profile/agentcobra57", + "contributions": [ + { + "type": "translation", + "url": "https://translate.castopod.org" + } + ] + }, + { + "login": "alephoto85", + "name": "Alessandro", + "avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/15094649/large/530391f54157af52ae33058ec15b0f99.jpg", + "profile": "https://crowdin.com/profile/alephoto85", + "contributions": [ + { + "type": "translation", + "url": "https://translate.castopod.org" + } + ] + }, + { + "login": "liimee", + "name": "liimee", + "avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg", + "profile": "https://crowdin.com/profile/liimee", + "contributions": [ + { + "type": "translation", + "url": "https://translate.castopod.org" + } + ] + }, + { + "login": "ahmedsabouni", + "name": "Ahmed Sabouni", + "avatar_url": "https://avatars.githubusercontent.com/u/74497842?v=4", + "profile": "https://github.com/ahmedsabouni", + "contributions": [ + { + "type": "translation", + "url": "https://translate.castopod.org" + } + ] + }, + { + "login": "KrzysztofDomanczyk", + "name": "KrzysztofDomanczyk", + "avatar_url": "https://avatars.githubusercontent.com/u/75178474?v=4", + "profile": "https://github.com/KrzysztofDomanczyk", + "contributions": ["code"] + }, + { + "login": "Dwev", + "name": "Guy Martin", + "avatar_url": "https://avatars.githubusercontent.com/u/46626050?v=4", + "profile": "https://github.com/Dwev", + "contributions": ["bug", "code"] + }, + { + "login": "prcutler", + "name": "Paul Cutler", + "avatar_url": "https://avatars.githubusercontent.com/u/67276?v=4", + "profile": "https://github.com/prcutler", + "contributions": ["doc", "question", "ideas"] + }, + { + "login": "nateritter", + "name": "Nate Ritter", + "avatar_url": "https://avatars.githubusercontent.com/u/198798?v=4", + "profile": "https://github.com/nateritter", + "contributions": ["code"] } ], "commitConvention": "none" diff --git a/docker/development/Dockerfile b/.devcontainer/Dockerfile similarity index 68% rename from docker/development/Dockerfile rename to .devcontainer/Dockerfile index b99f9539..cca42fb0 100644 --- a/docker/development/Dockerfile +++ b/.devcontainer/Dockerfile @@ -4,31 +4,20 @@ # ⚠️ NOT optimized for production # should be used only for development purposes #--------------------------------------------------- -FROM php:8.1-fpm +FROM php:8.5-fpm LABEL maintainer="Yassine Doghri " -COPY . /castopod -WORKDIR /castopod - # Install composer COPY --from=composer:2 /usr/bin/composer /usr/bin/composer # Install server requirements -RUN apt-get update \ - # gnupg to sign commits with gpg - && apt-get install --yes --no-install-recommends gnupg \ - # npm through the nodejs package - && curl -fsSL https://deb.nodesource.com/setup_16.x | bash - \ +RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ && apt-get update \ && apt-get install --yes --no-install-recommends nodejs \ - # update npm - && npm install --global npm@8 \ - && apt-get update \ - && apt-get install --yes --no-install-recommends \ - git \ + # gnupg to sign commits with gpg + gnupg \ openssh-client \ - vim \ # cron for scheduled tasks cron \ # unzip used by composer @@ -38,7 +27,7 @@ RUN apt-get update \ libicu-dev \ libpng-dev \ libwebp-dev \ - libjpeg-dev \ + libjpeg62-turbo-dev \ libfreetype6-dev \ zlib1g-dev \ libzip-dev \ @@ -58,11 +47,4 @@ RUN apt-get update \ && docker-php-ext-enable redis \ # mysqli for database access && docker-php-ext-install mysqli \ - && docker-php-ext-enable mysqli \ - # configure php - && echo "file_uploads = On\n" \ - "memory_limit = 512M\n" \ - "upload_max_filesize = 500M\n" \ - "post_max_size = 512M\n" \ - "max_execution_time = 300\n" \ - > /usr/local/etc/php/conf.d/uploads.ini + && docker-php-ext-enable mysqli diff --git a/.devcontainer/crontab b/.devcontainer/crontab new file mode 100644 index 00000000..8cef165a --- /dev/null +++ b/.devcontainer/crontab @@ -0,0 +1 @@ +* * * * * /usr/local/bin/php /workspaces/castopod/spark tasks:run >> /dev/null 2>&1 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fbd4eb36..f1c12034 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,46 +1,70 @@ // For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at: // https://github.com/microsoft/vscode-dev-containers/tree/v0.117.1/containers/docker-existing-dockerfile { - "name": "Castopod dev", - "dockerComposeFile": ["../docker-compose.yml", "./docker-compose.yml"], + "name": "castopod.local", + "dockerComposeFile": ["./docker-compose.yml"], "service": "app", - "workspaceFolder": "/castopod", - "postCreateCommand": "composer install && npm install && npm run build:static", - "postStartCommand": "crontab ./crontab && cron && php spark serve --host 0.0.0.0", - "postAttachCommand": "crontab ./crontab && service cron reload", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + "postCreateCommand": "composer install && pnpm install && pnpm run build:static && php spark migrate --all && php spark db:seed DevSeeder", + "postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder} && crontab .devcontainer/crontab && cron && php spark serve --host 0.0.0.0 --port ${APP_PORT:-8080}", + "postAttachCommand": "crontab .devcontainer/crontab && service cron reload", "shutdownAction": "stopCompose", - "settings": { - "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "[php]": { - "editor.defaultFormatter": "bmewburn.vscode-intelephense-client", - "editor.formatOnSave": false - }, - "css.validate": false, - "color-highlight.markerType": "dot-before", - "files.associations": { - "*.xml.dist": "xml", - "spark": "php", - "env": "dotenv", - ".rsync-filter": "diff" - } + "features": { + "ghcr.io/devcontainers/features/git:1": {}, + "ghcr.io/guiyomh/features/vim:0": {}, + "ghcr.io/NicoVIII/devcontainer-features/pnpm:2": {} }, - "extensions": [ - "bmewburn.vscode-intelephense-client", - "bradlc.vscode-tailwindcss", - "breezelin.phpstan", - "dbaeumer.vscode-eslint", - "eamodio.gitlens", - "esbenp.prettier-vscode", - "heybourn.headwind", - "jamesbirtles.svelte-vscode", - "kasik96.latte", - "mikestead.dotenv", - "naumovs.color-highlight", - "pflannery.vscode-versionlens", - "runem.lit-plugin", - "streetsidesoftware.code-spell-checker", - "stylelint.vscode-stylelint", - "wayou.vscode-todo-highlight" - ] + "customizations": { + "vscode": { + "settings": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "[php]": { + "editor.defaultFormatter": "bmewburn.vscode-intelephense-client", + "editor.formatOnSave": false + }, + "css.validate": false, + "color-highlight.markerType": "dot-before", + "files.associations": { + "*.xml.dist": "xml", + "spark": "php", + "env": "dotenv", + ".rsync-filter": "diff" + }, + "json.schemas": [ + { + "fileMatch": [ + "plugins/**/manifest.json", + "tests/modules/Plugins/mocks/manifests/*.json", + "tests/modules/Plugins/mocks/plugins/**/manifest.json" + ], + "url": "/workspaces/castopod/modules/Plugins/Manifest/manifest.schema.json" + } + ] + }, + "extensions": [ + "astro-build.astro-vscode", + "bmewburn.vscode-intelephense-client", + "bradlc.vscode-tailwindcss", + "breezelin.phpstan", + "DavidAnson.vscode-markdownlint", + "dbaeumer.vscode-eslint", + "eamodio.gitlens", + "esbenp.prettier-vscode", + "heybourn.headwind", + "jamesbirtles.svelte-vscode", + "kasik96.latte", + "mikestead.dotenv", + "naumovs.color-highlight", + "pflannery.vscode-versionlens", + "runem.lit-plugin", + "streetsidesoftware.code-spell-checker", + "stylelint.vscode-stylelint", + "unifiedjs.vscode-mdx", + "wayou.vscode-todo-highlight", + "yzhang.markdown-all-in-one", + "42Crunch.vscode-openapi" + ] + } + } } diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index ec3827cf..665b78e9 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,10 +1,76 @@ -version: "3" services: app: + build: + context: . + dockerfile: Dockerfile volumes: - # Mounts the project folder to '/workspace'. While this file is in .devcontainer, - # mounts are relative to the first file in the list, which is a level up. - - .:/castopod:cached + - ../..:/workspaces:cached + - ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini + environment: + APP_PORT: ${APP_PORT:-8080} # used in devcontainer.json file + VITE_PORT: ${VITE_PORT:-5173} # used in ../vite.config.js file + CI_ENVIRONMENT: development + vite_environment: development + app_forceGlobalSecureRequests: 0 #false + app_baseURL: http://localhost:${APP_PORT:-8080}/ + media_baseURL: http://localhost:${APP_PORT:-8080}/ + admin_gateway: cp-admin + auth_gateway: cp-auth + analytics_salt: dev_analytics_salt + database_default_hostname: mariadb + database_default_database: castopod + database_default_username: castopod + database_default_password: castopod + database_default_DBPrefix: cp_ + restapi_enabled: 1 #true + email_fromEmail: hello@castopod.local + email_SMTPCrypto: "" + email_SMTPHost: mailpit + email_SMTPUser: castopod + email_SMTPPass: castopod + email_SMTPPort: ${MAILPIT_SMTP_PORT:-1025} + depends_on: + - mariadb - # Overrides default command so things don't shut down after the process ends. - command: /bin/sh -c "while sleep 1000; do :; done" + mariadb: + image: mariadb:10.2 + volumes: + - ./initdb:/docker-entrypoint-initdb.d + - mariadb:/var/lib/mysql + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: castopod + MYSQL_USER: castopod + MYSQL_PASSWORD: castopod + + phpmyadmin: + image: phpmyadmin/phpmyadmin:latest + environment: + PMA_HOST: mariadb + PMA_PORT: 3306 + UPLOAD_LIMIT: 300M + ports: + - 8888:80 + volumes: + - phpmyadmin:/sessions + depends_on: + - mariadb + + mailpit: + image: axllent/mailpit + restart: always + volumes: + - mailpit:/data + ports: + - ${MAILPIT_WEBUI_PORT:-8025}:8025 + - ${MAILPIT_SMTP_PORT:-1025}:1025 + environment: + MP_MAX_MESSAGES: 5000 + MP_DATA_FILE: /data/mailpit.db + MP_SMTP_AUTH_ACCEPT_ANY: 1 + MP_SMTP_AUTH_ALLOW_INSECURE: 1 + +volumes: + mariadb: + phpmyadmin: + mailpit: diff --git a/initdb/01.sql b/.devcontainer/initdb/01.sql similarity index 100% rename from initdb/01.sql rename to .devcontainer/initdb/01.sql diff --git a/docker/production/app/uploads.ini b/.devcontainer/uploads.ini similarity index 100% rename from docker/production/app/uploads.ini rename to .devcontainer/uploads.ini diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..1b773dc8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,68 @@ +.env + +.git/ +node_modules/ +vendor/ +build/ +docs/ +scripts/ +tests/ + +#------------------------- +# Temporary Files +#------------------------- +writable/cache/* +!writable/cache/index.html + +writable/logs/* +!writable/logs/index.html + +writable/session/* +!writable/session/index.html + +writable/temp/* +!writable/temp/index.html + +writable/uploads/* +!writable/uploads/index.html + +writable/debugbar/* +!writable/debugbar/index.html + +# public folder +public/* +!public/media +!public/.htaccess +!public/favicon.ico +!public/icon* +!public/castopod-banner* +!public/castopod-avatar* +!public/index.php +!public/robots.txt +!public/.well-known +!public/.well-known/GDPR.yml + +public/assets/* +!public/assets/index.html + +# public media folder +!public/media/podcasts +!public/media/persons +!public/media/site + +public/media/podcasts/* +!public/media/podcasts/index.html + +public/media/persons/* +!public/media/persons/index.html + +public/media/site/* +!public/media/site/index.html + +# Generated files +modules/Admin/Language/*/PersonsTaxonomy.php + +# Castopod bundle & packages +castopod/ +castopod-*.zip +castopod-*.tar.gz diff --git a/.env.example b/.env.example index 9a486a1f..40e4c336 100644 --- a/.env.example +++ b/.env.example @@ -14,9 +14,10 @@ # Instance configuration #-------------------------------------------------------------------- app.baseURL="https://YOUR_DOMAIN_NAME/" -app.mediaBaseURL="https://YOUR_MEDIA_DOMAIN_NAME/" +media.baseURL="https://YOUR_MEDIA_DOMAIN_NAME/" admin.gateway="cp-admin" auth.gateway="cp-auth" +analytics.salt="RANDOM_STRING_OF_64_CHARACTERS" #-------------------------------------------------------------------- # Database configuration @@ -50,9 +51,20 @@ cache.handler="file" # cache.redis.port=6379 # cache.redis.database=0 +#-------------------------------------------------------------------- +# S3 configuration +#-------------------------------------------------------------------- +# media.fileManager="s3" +# media.s3.endpoint="your_s3_host" +# media.s3.key="your_s3_key" +# media.s3.secret="your_s3_secret" +# media.s3.region="your_s3_region" + #-------------------------------------------------------------------- # REST API configuration #-------------------------------------------------------------------- # restapi.enabled=true - +# restapi.basicAuthUsername=castopod +# restapi.basicAuthPassword=password +# restapi.basicAuth=true diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index cff4e85e..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "env": { - "browser": true, - "es2020": true - }, - "parser": "@typescript-eslint/parser", - "plugins": ["@typescript-eslint"], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:prettier/recommended" - ], - "parserOptions": { - "ecmaVersion": 11, - "sourceType": "module" - }, - "rules": {} -} diff --git a/.gitignore b/.gitignore index 35dd7290..b7ea048c 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,7 @@ writable/uploads/* !writable/uploads/index.html writable/debugbar/* +!writable/debugbar/index.html php_errors.log @@ -85,6 +86,7 @@ tests/coverage* # Don't save phpunit under version control. phpunit +.phpunit.cache #------------------------- # Composer @@ -105,15 +107,15 @@ _modules/* .idea/ *.iml -# Netbeans -nbproject/ -build/ -nbbuild/ -dist/ -nbdist/ -nbactions.xml -nb-configuration.xml -.nb-gradle/ +# NetBeans +/nbproject/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/nbactions.xml +/nb-configuration.xml +/.nb-gradle/ # Sublime Text *.tmlanguage.cache @@ -126,30 +128,36 @@ nb-configuration.xml # Visual Studio Code .vscode/ +.history/ +tmp/ /results/ /phpunit*.xml -/.phpunit.*.cache -# npm +# js package manager yarn.lock node_modules +.pnpm-store # JS .cache # public folder public/* -public/media/site !public/media !public/.htaccess !public/favicon.ico !public/icon* +!public/castopod-banner* +!public/castopod-avatar* !public/index.php !public/robots.txt !public/.well-known !public/.well-known/GDPR.yml +public/assets/* +!public/assets/index.html + # public media folder !public/media/podcasts !public/media/persons @@ -167,15 +175,13 @@ public/media/site/* # Generated files modules/Admin/Language/*/PersonsTaxonomy.php -#------------------------- -# Docker volumes -#------------------------- - -mariadb -phpmyadmin -sessions - # Castopod bundle & packages castopod/ castopod-*.zip castopod-*.tar.gz + +# Plugins +plugins/* +!plugins/.gitkeep +writable/plugins.json +writable/plugins-lock.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d17fbda3..32076043 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,32 +1,52 @@ -image: code.castopod.org:5050/adaures/castopod:latest +image: code.castopod.org:5050/adaures/castopod:ci-php8.5 stages: - prepare - quality - bundle - release - - build - deploy + - build php-dependencies: stage: prepare script: # Install all php dependencies - composer install --prefer-dist --no-ansi --no-interaction --no-progress --ignore-platform-reqs + cache: + key: + files: + - composer.lock + paths: + - .composer-cache artifacts: + expire_in: 30 mins paths: - vendor/ - expire_in: 30 mins + rules: + - if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/ + when: never + - when: on_success js-dependencies: stage: prepare script: - # Install all npm dependencies - - npm ci + # Install all js dependencies + - pnpm install + cache: + key: + files: + - pnpm-lock.yaml + paths: + - .pnpm-store artifacts: + expire_in: 30 mins paths: - node_modules/ - expire_in: 30 mins + rules: + - if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/ + when: never + - when: on_success lint-commit-msg: stage: quality @@ -36,11 +56,10 @@ lint-commit-msg: - ./scripts/lint-commit-msg.sh dependencies: - js-dependencies - only: - - develop - - main - - beta - - alpha + rules: + - if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/ + when: never + - if: $CI_COMMIT_BRANCH =~ /^(develop|main|alpha|beta|next)$/ lint-php: stage: quality @@ -53,37 +72,46 @@ lint-php: - vendor/bin/rector process --dry-run --ansi dependencies: - php-dependencies + rules: + - if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/ + when: never + - when: on_success lint-js: stage: quality script: - - npm run prettier - - npm run typecheck - - npm run lint - - npm run lint:css + - pnpm run format + - pnpm run typecheck + - pnpm run lint + - pnpm run lint:css dependencies: - js-dependencies + rules: + - if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/ + when: never + - when: on_success tests: stage: quality services: - - mariadb + - mariadb:10.11 variables: MYSQL_ROOT_PASSWORD: "R00Tp4ssW0RD" MYSQL_DATABASE: "test" MYSQL_USER: "castopod" MYSQL_PASSWORD: "castopod" - script: - - apt-get update && apt-get install -y mariadb-client libmariadb-dev - - - echo "SHOW DATABASES;" | mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mariadb "$MYSQL_DATABASE" + - echo "SHOW DATABASES;" | mariadb --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mariadb "$MYSQL_DATABASE" --skip_ssl # run phpunit without code coverage # TODO: add code coverage - vendor/bin/phpunit --no-coverage dependencies: - php-dependencies + rules: + - if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/ + when: never + - when: on_success bundle: stage: bundle @@ -104,13 +132,12 @@ bundle: name: "castopod-${CI_COMMIT_REF_SLUG}_${CI_COMMIT_SHORT_SHA}" paths: - castopod - only: - variables: - - $CI_PROJECT_NAMESPACE == "adaures" - except: - - main - - beta - - alpha + rules: + - if: $CI_PROJECT_NAMESPACE != "adaures" + when: never + - if: $CI_COMMIT_BRANCH =~ /^(main|alpha|beta|next)$/ || $CI_COMMIT_TAG + when: never + - when: on_success release: stage: release @@ -127,48 +154,45 @@ release: - chmod +x ./scripts/package.sh # run semantic-release script (configured in `.releaserc.json` file) - - npm run release + - pnpm run release dependencies: - php-dependencies - js-dependencies artifacts: paths: - castopod - - CP_VERSION.env - only: - - main - - beta - - alpha + rules: + - if: $CI_PROJECT_NAMESPACE != "adaures" + when: never + - if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/ + when: never + - if: $CI_COMMIT_BRANCH =~ /^(main|alpha|beta|next)$/ website: stage: deploy trigger: adaures/castopod.org - only: - - main - - beta - - alpha + rules: + - if: $CI_PROJECT_NAMESPACE != "adaures" + when: never + - if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/ && $CI_COMMIT_TAG documentation: stage: deploy trigger: include: docs/.gitlab-ci.yml strategy: depend - only: - changes: - - docs/**/* + rules: + - if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/ + when: never + - when: on_success docker: stage: build trigger: include: docker/production/.gitlab-ci.yml strategy: depend - variables: - PARENT_PIPELINE_ID: $CI_PIPELINE_ID - only: - refs: - - develop - - main - - beta - - alpha - variables: - - $CI_PROJECT_NAMESPACE == "adaures" + rules: + - if: $CI_PROJECT_NAMESPACE != "adaures" + when: never + - if: $CI_COMMIT_BRANCH == "develop" + - if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/ && $CI_COMMIT_TAG diff --git a/.gitlab/issue_templates/bug.md b/.gitlab/issue_templates/bug.md index 7fec749a..c6041a61 100644 --- a/.gitlab/issue_templates/bug.md +++ b/.gitlab/issue_templates/bug.md @@ -6,7 +6,7 @@ 1. [First step] 2. [Second step] -3. [and so on...] +3. [and so on…] ### Expected behavior @@ -27,7 +27,7 @@ logs, and code as it's very hard to read otherwise. - OS: [e.g. Ubuntu server] - Browser: [e.g. chrome, safari] - Web server: [eg. Apache] -- [any other relevant context...] +- [any other relevant context…] ### Possible fixes diff --git a/.gitlab/issue_templates/feature-request.md b/.gitlab/issue_templates/feature-request.md index 644a8fd4..0886d392 100644 --- a/.gitlab/issue_templates/feature-request.md +++ b/.gitlab/issue_templates/feature-request.md @@ -1,7 +1,7 @@ ### Is your feature request related to a problem? Please describe A clear and concise description of what the problem is. Ex. I'm always -frustrated when [...] +frustrated when […] ### Describe the solution you'd like diff --git a/.husky/commit-msg b/.husky/commit-msg index cdb7c8da..062719b1 100755 --- a/.husky/commit-msg +++ b/.husky/commit-msg @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -npx --no-install commitlint --verbose --edit "$1" +pnpm exec commitlint --verbose --edit "$1" diff --git a/.husky/pre-commit b/.husky/pre-commit index 39c89b16..12ee980f 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,11 +1,8 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - # CaptainHook 5.10.0 INTERACTIVE="--no-interaction" vendor/bin/captainhook $INTERACTIVE --configuration=captainhook.json --bootstrap=vendor/autoload.php hook:pre-commit "$@" <&0 -npm run typecheck -npx lint-staged +pnpm run typecheck +pnpm exec lint-staged diff --git a/.husky/pre-push b/.husky/pre-push index 9b4d0f80..a3617f04 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,6 +1,3 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - # CaptainHook 5.10.0 INTERACTIVE="--no-interaction" diff --git a/.prettierrc.json b/.prettierrc.json index d567a64c..cae76d94 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -2,7 +2,7 @@ "trailingComma": "es5", "overrides": [ { - "files": "*.md", + "files": ["*.md", "*.mdx"], "options": { "proseWrap": "always" } diff --git a/.releaserc.json b/.releaserc.json index a9deb773..7a8b6584 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -8,16 +8,79 @@ { "name": "beta", "prerelease": true + }, + { + "name": "next", + "prerelease": true } ], "plugins": [ - "@semantic-release/commit-analyzer", - "@semantic-release/release-notes-generator", + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "type": "docs", + "scope": "README", + "release": "patch" + }, + { + "type": "refactor", + "scope": "core-*", + "release": "minor" + }, + { + "type": "refactor", + "release": "patch" + } + ], + "parserOpts": { + "noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES", "BREAKING"] + } + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "parserOpts": { + "noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES", "BREAKING"] + }, + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Internal", + "hidden": false + }, + { + "type": "refactor", + "section": "Internal", + "hidden": false + }, + { + "type": "perf", + "section": "Internal", + "hidden": false + } + ] + } + } + ], "@semantic-release/changelog", [ "@semantic-release/exec", { - "prepareCmd": "./scripts/bundle.sh ${nextRelease.version} && ./scripts/package.sh ${nextRelease.version} && npx prettier --write CHANGELOG.md" + "prepareCmd": "./scripts/bundle.sh ${nextRelease.version} && ./scripts/package.sh ${nextRelease.version} && pnpm exec prettier --write CHANGELOG.md" } ], "@semantic-release/npm", @@ -30,7 +93,8 @@ "package.json", "package-lock.json", "CHANGELOG.md" - ] + ], + "message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}" } ], [ diff --git a/.rsync-filter b/.rsync-filter index 674896fa..0fcb79ec 100644 --- a/.rsync-filter +++ b/.rsync-filter @@ -1,8 +1,10 @@ # rsync filter rules to copy required files for Castopod's bundle -- app/Resources/ ++ resources/icons/*** ++ resources/ + app/*** + modules/*** ++ plugins/*** + public/*** + themes/*** + vendor/*** @@ -11,4 +13,5 @@ + LICENSE.md + README.md + spark ++ php-icons.php - ** diff --git a/.stylelintrc.json b/.stylelintrc.json index 2cd0132c..8ea025c1 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,5 +1,5 @@ { - "extends": "stylelint-config-recommended", + "extends": "stylelint-config-standard", "rules": { "at-rule-no-unknown": [ true, @@ -10,10 +10,24 @@ "responsive", "variants", "screen", - "layer" + "layer", + "config" ] } ], - "no-descending-specificity": null + "at-rule-no-deprecated": [ + true, + { + "ignoreAtRules": ["apply"] + } + ], + "function-no-unknown": [ + true, + { + "ignoreFunctions": ["theme"] + } + ], + "no-descending-specificity": null, + "selector-class-pattern": null } } diff --git a/.svgo.js b/.svgo.cjs similarity index 100% rename from .svgo.js rename to .svgo.cjs diff --git a/.svgo.icons.js b/.svgo.icons.cjs similarity index 100% rename from .svgo.icons.js rename to .svgo.icons.cjs diff --git a/CHANGELOG.md b/CHANGELOG.md index b6d4cebb..29f4a170 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,1860 @@ +## [2.0.0-next.3](https://code.castopod.org/adaures/castopod/compare/v2.0.0-next.2...v2.0.0-next.3) (2024-12-30) + +### Features + +- **api:** add Episode create and publish endpoints + ([a90cdfd](https://code.castopod.org/adaures/castopod/commit/a90cdfdcdbde7a8fb520c6815d7b757947aea055)) +- **image:** add image size's width and height + ([f50098e](https://code.castopod.org/adaures/castopod/commit/f50098ec8926c8ae40718f5f128b6de7fe721b46)) +- **plugins:** add defaultValue for all field types + ([d3a98db](https://code.castopod.org/adaures/castopod/commit/d3a98db6d0112b5f59daddd2708c09dd2e595332)) +- **plugins:** add group field type + multiple option to render field arrays + ([11ccd0e](https://code.castopod.org/adaures/castopod/commit/11ccd0ebe71d476d8c0dbfe28edcf01f7f362b83)) +- **plugins:** add html field type + CodeEditor component + rework html head + generation + ([8cf9c6d](https://code.castopod.org/adaures/castopod/commit/8cf9c6dc833aedcccbc4cdb309b111f84d97d629)) +- **rss:** add option for 301 redirect to new feed url + ([8402cc2](https://code.castopod.org/adaures/castopod/commit/8402cc29d2d0c61b014a7e03e5ccce7d3c11782a)) + +### Bug Fixes + +- add downloads_count to episodes table, computed every hour + ([f981937](https://code.castopod.org/adaures/castopod/commit/f9819376455c371eb5bd3c84ad938698335a3d67)) +- allow passing json to app.proxyIPs config to set it + ([cbf739e](https://code.castopod.org/adaures/castopod/commit/cbf739e95cc0ad6e83a21353b8f4678e68d74f63)) +- **api:** cast integers when creating episode + ([775b302](https://code.castopod.org/adaures/castopod/commit/775b302f7c886e30e133c8a8c68764301b6c663b)) +- **docker-image:** clear cache to account for new assets and data structure + changes + ([63c763f](https://code.castopod.org/adaures/castopod/commit/63c763f941195b3758c4b91acd8c350a5e7bb9c2)), + closes [#510](https://code.castopod.org/adaures/castopod/issues/510) +- edit remap functions to get episode in episode admin controllers + ([9f74cca](https://code.castopod.org/adaures/castopod/commit/9f74cca342fedd896977efd2e89d0143959f3c4f)) +- **episode:** do not change slug when editing episode title + ([a83afb0](https://code.castopod.org/adaures/castopod/commit/a83afb0004511db80337806577fbc36f8d777116)), + closes [#513](https://code.castopod.org/adaures/castopod/issues/513) +- **fediverse:** add "processing" and "failed" statuses to better manage + broadcast load + ([1d7583d](https://code.castopod.org/adaures/castopod/commit/1d7583d738219574ae3d45d294dc94e7e406472b)), + closes [#511](https://code.castopod.org/adaures/castopod/issues/511) +- **icons:** set correct names for lock and lock-unlock icons in premium banner + ([37ee6d3](https://code.castopod.org/adaures/castopod/commit/37ee6d35b4bb66ce23dc271fb846200d1be0e7f6)) +- **plugins:** clear cache after activating or deactivating plugin + ([08c7df2](https://code.castopod.org/adaures/castopod/commit/08c7df2a5d5be340490c78deeef823167eb1b2fc)) +- **plugins:** delete relevant cache when submitting settings + ([00bd4c0](https://code.castopod.org/adaures/castopod/commit/00bd4c02ee23b181d74e7731626bfec3b1ff4916)) +- **podcast-model:** always query podcast from database when clearing cache + ([d30c49c](https://code.castopod.org/adaures/castopod/commit/d30c49cdff380c15db4f1851631a255a5baffcbe)) +- **premium-podcasts:** update query to validate subscription + ([2b1bbf3](https://code.castopod.org/adaures/castopod/commit/2b1bbf34303ead927f433b5c7d5d888ca3799954)) +- **preview:** delete episode preview cache after editing episode + ([732d429](https://code.castopod.org/adaures/castopod/commit/732d42923d0d7a66ff1ebd5841458e4205060560)), + closes [#514](https://code.castopod.org/adaures/castopod/issues/514) +- **release:** add conventional-changelog-conventionalcommits for CHANGELOG + generation + ([6934c8a](https://code.castopod.org/adaures/castopod/commit/6934c8aa8f0b7f9eea7c3f6f4089c56b2391d9a6)) +- **rss:** add subscription id to cache name to prevent premium feeds from + overlapping + ([74f9325](https://code.castopod.org/adaures/castopod/commit/74f9325946d03a0d4efce57045e41cc9454ff97c)) +- set user as www-data when running cron jobs in docker's supervisord config + ([65d74f1](https://code.castopod.org/adaures/castopod/commit/65d74f14e612be3757c9304518eee112705f5ff9)) +- typo in EpisodeController remap function to get episode + ([f288a75](https://code.castopod.org/adaures/castopod/commit/f288a750f580ab19b04a170cc76bf8769084e19d)) +- update select and multi-select options to value/label arrays + ([63f93f5](https://code.castopod.org/adaures/castopod/commit/63f93f585bec4a11022cc8c75deb34968cba2348)) + +### Internal + +- **plugins:** create Field objects per field type in settings forms + handle + rendering in class + ([34be5bc](https://code.castopod.org/adaures/castopod/commit/34be5bccabb7531afdcc6ebaf1dd39e4dfbe0677)) +- remove fields from podcast and episode entities to be replaced with plugins + ([b869acb](https://code.castopod.org/adaures/castopod/commit/b869acb3a988a3616d883a41c25d9c8409bd5518)) +- rename controller methods for views and actions to be more consistent + ([85704bf](https://code.castopod.org/adaures/castopod/commit/85704bfbe03fe5e38ff5e76a0e1cf0e5f1275f57)) +- update CodeIgniter to v4.5.6 + ([f295e9a](https://code.castopod.org/adaures/castopod/commit/f295e9aa4ca3129df24a22779f7c19bba7fac370)) +- update codigniter-icons to v1.0.1 + ([fa6967e](https://code.castopod.org/adaures/castopod/commit/fa6967e65cef1705b19cbb205132c4c751507d53)) +- update js dependencies to latest + ([70c9797](https://code.castopod.org/adaures/castopod/commit/70c97971fcf5bbeee826578057ae0e3afbbbd8a8)) + +# [2.0.0-next.2](https://code.castopod.org/adaures/castopod/compare/v2.0.0-next.1...v2.0.0-next.2) (2024-07-08) + +### Bug Fixes + +- **audio-player:** set player icons to default instead of missing Castopod's + ([0ba0a25](https://code.castopod.org/adaures/castopod/commit/0ba0a25b11bd67aeeb47a8179b72152dfd4a36da)) +- broken icon call in frontend default pages template + ([3228362](https://code.castopod.org/adaures/castopod/commit/322836254e86be7878e21438177ee8f73f03a2fa)) +- **manifest:** set repository url as required in docstring typings + ([a8c81b3](https://code.castopod.org/adaures/castopod/commit/a8c81b3fa19a28dbd608027c231dcac31eafb38f)) +- set correct icons parameters in map and funding links views + ([5d35524](https://code.castopod.org/adaures/castopod/commit/5d355248753be24e3cf324144ff076f2fc23be88)), + closes [#500](https://code.castopod.org/adaures/castopod/issues/500) + +### Features + +- **plugins:** add `minCastopodVersion` to denote incompatibility with previous + Castopod versions + ([fc9ea75](https://code.castopod.org/adaures/castopod/commit/fc9ea7597e454e5c7c7af043d29af7bbe119e342)) +- **plugins:** load and display LICENSE.md file if found in plugin's directory + ([fee7905](https://code.castopod.org/adaures/castopod/commit/fee7905935a9adf963b4485b437fe4d972c14b5f)) + +# [2.0.0-next.1](https://code.castopod.org/adaures/castopod/compare/v1.11.0...v2.0.0-next.1) (6/19/2024) + +### Bug Fixes + +- add missing php-icons config file to bundle + ([56612f0](https://code.castopod.org/adaures/castopod/commit/56612f0c762aa2d98e3c8c77fba88ffdf6f46a44)) +- **docs:** add base to og image using env variable + ([fe67659](https://code.castopod.org/adaures/castopod/commit/fe676590f23a33bdbe8905d234760923c029e350)) +- **import:** rewrite download_file helper to output curl response directly to + file + ([eb7ad2f](https://code.castopod.org/adaures/castopod/commit/eb7ad2f7e1c0137f222f47e47062887de42c4824)) +- include app/Resources/icons folder to bundle + ([3fd5efc](https://code.castopod.org/adaures/castopod/commit/3fd5efc7956977acc19e53182f25b12813964a7d)) +- **platforms:** add platforms service + reduce memory consumption when + rendering platform cards + ([fe73e9f](https://code.castopod.org/adaures/castopod/commit/fe73e9fae9ea5d5ce946680aec194308bb2e620c)) +- set owner email visibility when editing podcast + ([fc4f982](https://code.castopod.org/adaures/castopod/commit/fc4f9825568cd4384c5b3cfe972accd146548807)), + closes [#473](https://code.castopod.org/adaures/castopod/issues/473) + +### Build System + +- release next major version as prerelease + ([8275226](https://code.castopod.org/adaures/castopod/commit/827522643e9f8a5ea9be05b4847dc637f0f43a13)) + +### Features + +- add Plugins module with base files for plugins architecture + ([7253e13](https://code.castopod.org/adaures/castopod/commit/7253e13ac2118f6f165f54ea0cbcd63d51ab9205)) +- **plugins:** abstract settings form for general, podcast and episode types + ([b62b483](https://code.castopod.org/adaures/castopod/commit/b62b483ad9ff114a22a9ee52e1a1a2c9fa444d42)) +- **plugins:** activate / deactivate plugin using settings table + ([27d2a1b](https://code.castopod.org/adaures/castopod/commit/27d2a1b0ffba9454dd54cbb4251a2d179b09762a)) +- **plugins:** add aside with plugin metadata next to plugin's readme + ([dfb7888](https://code.castopod.org/adaures/castopod/commit/dfb7888aeb689b4066abc37084e08cd7f1d0f15d)) +- **plugins:** add before channel/item hooks to allow podcast/episode data edit + when generating rss + ([80d2c48](https://code.castopod.org/adaures/castopod/commit/80d2c48ee265cb32ed0d710c488292fcbc120044)) +- **plugins:** add json schema definition for plugin manifest + ([b5eddf3](https://code.castopod.org/adaures/castopod/commit/b5eddf351f6f6fa1c299fbac31cbd056ef232330)) +- **plugins:** add methods to easily retrieve general, podcast and episode + settings in hooks methods + ([3a900bb](https://code.castopod.org/adaures/castopod/commit/3a900bbab68b819cedf8943540d2ee0aeb6e8539)) +- **plugins:** add new field types + validate & cast user data before storing + settings + ([6f833fc](https://code.castopod.org/adaures/castopod/commit/6f833fc76a3aa6c6b87c27ad18a2fb90e537e21e)) +- **plugins:** add options to manifest for building forms and storing plugin + settings + ([3d8aedf](https://code.castopod.org/adaures/castopod/commit/3d8aedf9c34e6927b6d3b11445d5f0e669b347d7)) +- **plugins:** add settings page for podcast and episode if defined in the + plugin's manifest + ([89ac92f](https://code.castopod.org/adaures/castopod/commit/89ac92fb412a04231ce52fd6480c9ab893b19ef5)) +- **plugins:** add siteHead hook to add custom meta tags to public pages + ([e80a33b](https://code.castopod.org/adaures/castopod/commit/e80a33bf2ad4fe1b47037add7470a6c2770f4036)) +- **plugins:** display errors when plugin is invalid instead of crashing + ([8ec7909](https://code.castopod.org/adaures/castopod/commit/8ec79097bbdbcbce622518ef61c068f20e0ef74e)) +- **plugins:** handle empty states and long strings in UI + ([45ac2a4](https://code.castopod.org/adaures/castopod/commit/45ac2a4be96532b9456e6af1d26ba4ada3649303)) +- **plugins:** load and validate plugin manifest.json + ([1510e36](https://code.castopod.org/adaures/castopod/commit/1510e36c0acd2b254622ec230acd1d2461ee9bf3)) +- **plugins:** load plugins using file locator service + ([587938d](https://code.castopod.org/adaures/castopod/commit/587938d2bf307b823af143586b9ec9e9b44e8dc1)) +- **plugins:** load README.md file to view plugin's instructions in UI + ([e6bfdfc](https://code.castopod.org/adaures/castopod/commit/e6bfdfc3902705285701c13c8067fe0f538425c6)) +- **plugins:** register plugins using Plugin.php file instead of namespace + + simplify i18n structure + ([2035c39](https://code.castopod.org/adaures/castopod/commit/2035c39fd138a1fd408516bd1972ab6a02544c10)) +- **plugins:** uninstall plugins via CLI and admin UI + ([9a80de4](https://code.castopod.org/adaures/castopod/commit/9a80de40686bbf4288da21cc2a6dde8036580e47)) +- set owner email to hidden by default in podcast create form + ([7a6d9df](https://code.castopod.org/adaures/castopod/commit/7a6d9df6db8a6184b8250ced0475f3e741dde7f4)) +- support podcast:txt tag with verify use case + ([57e459e](https://code.castopod.org/adaures/castopod/commit/57e459e187ed048430f4137172e22396cd02bf81)), + closes [#468](https://code.castopod.org/adaures/castopod/issues/468) + +### BREAKING CHANGES + +- next major release including plugins architecture + +# [1.11.0](https://code.castopod.org/adaures/castopod/compare/v1.10.5...v1.11.0) (4/17/2024) + +### Bug Fixes + +- **premium:** set itunes:block on premium feeds to prevent indexing + ([88851b0](https://code.castopod.org/adaures/castopod/commit/88851b022663d575a816f0e2f33f0353767dd52d)) +- **rss:** generate podcast guid if empty + ([a5aef2a](https://code.castopod.org/adaures/castopod/commit/a5aef2a63e464632f3941649d455672835989e6c)), + closes [#450](https://code.castopod.org/adaures/castopod/issues/450) + +### Features + +- add trailer tags to rss if trailer episodes are present + ([80fdd9c](https://code.castopod.org/adaures/castopod/commit/80fdd9cfb4a95feac6ed0000435a013fc83e6892)) +- add transcript display to episode page + ([4d141fc](https://code.castopod.org/adaures/castopod/commit/4d141fceae56fa9e666b42c32a830ff9c68989db)), + closes [#411](https://code.castopod.org/adaures/castopod/issues/411) +- **platforms:** add telegram to socials + ([004f804](https://code.castopod.org/adaures/castopod/commit/004f804045cd8e884361bb4318109fbdd7afc9a8)) +- **platforms:** add truefans.fm and episodes.fm + ([d046ecc](https://code.castopod.org/adaures/castopod/commit/d046ecc52f6ccd41d09f6de48e00d2c61d25d7f0)), + closes [#458](https://code.castopod.org/adaures/castopod/issues/458) + [#459](https://code.castopod.org/adaures/castopod/issues/459) + +## [1.10.5](https://code.castopod.org/adaures/castopod/compare/v1.10.4...v1.10.5) (3/12/2024) + +### Bug Fixes + +- **file-uploads:** validate chapters json content + remove permit_empty rule to + uploaded files + ([6289c42](https://code.castopod.org/adaures/castopod/commit/6289c42b1189f074c7e4e4cd9fbfd73bf26625c9)), + closes [#445](https://code.castopod.org/adaures/castopod/issues/445) + +## [1.10.4](https://code.castopod.org/adaures/castopod/compare/v1.10.3...v1.10.4) (2/26/2024) + +### Bug Fixes + +- display chapters in episode preview page + ([797516a](https://code.castopod.org/adaures/castopod/commit/797516a2ec7d88704412a5cca50421e8eef38eec)), + closes [#445](https://code.castopod.org/adaures/castopod/issues/445) + +## [1.10.3](https://code.castopod.org/adaures/castopod/compare/v1.10.2...v1.10.3) (2/21/2024) + +### Bug Fixes + +- **chapters:** use episode cover when chapter img is an empty string + ([a343de4](https://code.castopod.org/adaures/castopod/commit/a343de4cf6ba38561b8fe675fa9c38d9f0ecfec7)), + closes [#444](https://code.castopod.org/adaures/castopod/issues/444) +- **import:** set episodes as premium if podcast is set as premium by default + ([dfd66be](https://code.castopod.org/adaures/castopod/commit/dfd66beebfcca1670b0a9d389e8e3f8d2d08d2f2)) + +## [1.10.2](https://code.castopod.org/adaures/castopod/compare/v1.10.1...v1.10.2) (2/20/2024) + +### Bug Fixes + +- **podcast-import:** move closing parenthasis when checking for owner name and + email existence + ([cec7815](https://code.castopod.org/adaures/castopod/commit/cec78155f94a222edcf7964c0a2f3a3e0f46a98d)) + +## [1.10.1](https://code.castopod.org/adaures/castopod/compare/v1.10.0...v1.10.1) (2/20/2024) + +### Bug Fixes + +- **fediverse:** use config name to get Fediverse config properties instead of + hardcoded class string + ([5fd0980](https://code.castopod.org/adaures/castopod/commit/5fd0980ff7101d45051a2daa3f635694f85609d7)) + +# [1.10.0](https://code.castopod.org/adaures/castopod/compare/v1.9.0...v1.10.0) (2/19/2024) + +### Bug Fixes + +- **op3:** move op3 prefix to enclosure url instead of audio proxy + ([d580369](https://code.castopod.org/adaures/castopod/commit/d5803692357952d82d54efd8d3aa71de3a1c9571)) +- **podcast-import:** rollback transaction before exception is thrown + ([419bb04](https://code.castopod.org/adaures/castopod/commit/419bb04716088586b87b2c8f24a954ca8cfd6c76)), + closes [#429](https://code.castopod.org/adaures/castopod/issues/429) + [#319](https://code.castopod.org/adaures/castopod/issues/319) + [#443](https://code.castopod.org/adaures/castopod/issues/443) + [#438](https://code.castopod.org/adaures/castopod/issues/438) + +### Features + +- add podcast:season and podcast:episode tags to rss feed + ([98c6658](https://code.castopod.org/adaures/castopod/commit/98c6658840eedd55bd6d8042f8a69c342b87cd71)) +- add support for podcasting 2.0 "medium" tag with podcast, music and audiobook + ([630e788](https://code.castopod.org/adaures/castopod/commit/630e788f0e1ddfe5de229bd415a8e15361efa746)), + closes [#439](https://code.castopod.org/adaures/castopod/issues/439) +- display chapters in episode's public page + ([87cc437](https://code.castopod.org/adaures/castopod/commit/87cc437e1ead5486ed46ca37e2055aaf5c9445c1)), + closes [#423](https://code.castopod.org/adaures/castopod/issues/423) +- support VTT transcript file format in addition to SRT + ([7071b4b](https://code.castopod.org/adaures/castopod/commit/7071b4b6f48cb9a2f766064f3a5c23f92b293718)), + closes [#433](https://code.castopod.org/adaures/castopod/issues/433) + +# [1.9.0](https://code.castopod.org/adaures/castopod/compare/v1.8.2...v1.9.0) (1/31/2024) + +### Bug Fixes + +- **i18n:** escape language strings in form fields to prevent them from + disappearing + ([3cb5ffd](https://code.castopod.org/adaures/castopod/commit/3cb5ffd25b9604a83cd12935e641dab7c88fba47)), + closes [#412](https://code.castopod.org/adaures/castopod/issues/412) +- **podcast-about:** update stats query to discard scheduled episodes from + episodes number + ([67c037c](https://code.castopod.org/adaures/castopod/commit/67c037c9eb1e15c6945eaf74ec0ff30b33f4b704)) +- **premium-subs:** clear subscription list cache after insert + ([2accb0f](https://code.castopod.org/adaures/castopod/commit/2accb0f7652330b29c3adb85a2e1b0d5d83f1389)), + closes [#430](https://code.castopod.org/adaures/castopod/issues/430) +- **s3:** remove proxy, set objects acl to public-read, and serve files using + their public urls + ([6a77a9d](https://code.castopod.org/adaures/castopod/commit/6a77a9d2f29c849775a3d1bcbd819f73f21d9aa6)) + +### Features + +- add actor domain to handle in follow page + ([de099ac](https://code.castopod.org/adaures/castopod/commit/de099ac64300b8edb86e387fde89c0a3e9472f46)) +- **admin:** add podcast's OP3 analytics dashboard link + ([5f3752b](https://code.castopod.org/adaures/castopod/commit/5f3752b4430f6f2d5f9e5f6a7a003bc4d2f9d487)) + +## [1.8.2](https://code.castopod.org/adaures/castopod/compare/v1.8.1...v1.8.2) (1/17/2024) + +### Bug Fixes + +- **transcript:** add condition when concatenating sub text to prevent second + line duplication + ([6cbfec0](https://code.castopod.org/adaures/castopod/commit/6cbfec0d7d9bf85c8014d379026648857ea13373)) + +## [1.8.1](https://code.castopod.org/adaures/castopod/compare/v1.8.0...v1.8.1) (1/16/2024) + +### Bug Fixes + +- **models:** set updatedField as empty string when not used + ([164f4d3](https://code.castopod.org/adaures/castopod/commit/164f4d3be74ec8d371fb40d7fe730f7b2940ca05)) + +# [1.8.0](https://code.castopod.org/adaures/castopod/compare/v1.7.4...v1.8.0) (1/15/2024) + +### Bug Fixes + +- **episode-form:** add required validation rules for title and slug + ([30a3473](https://code.castopod.org/adaures/castopod/commit/30a34738635bf4f4a4c6b2a7174f7e439f0dfc6e)), + closes [#420](https://code.castopod.org/adaures/castopod/issues/420) +- **import:** check for empty string when generating podcast guid for feeds not + including one + ([ac5336f](https://code.castopod.org/adaures/castopod/commit/ac5336fbc5fb8038de541dd06938a8beb2e8d733)) +- **install:** add created superadmin to most powerful group in instance, ie. + superadmin + ([2ed511f](https://code.castopod.org/adaures/castopod/commit/2ed511f8a0005dc06eda5afd6b1d13beee1eb9dd)) +- **persons:** delete person avatar when deleting a person + ([c1ec98c](https://code.castopod.org/adaures/castopod/commit/c1ec98c95656844712011ff30b84c397b78da311)), + closes [#419](https://code.castopod.org/adaures/castopod/issues/419) +- **platforms:** add matrix.org as a social platform + ([9178c3f](https://code.castopod.org/adaures/castopod/commit/9178c3f3afa16e104d25ae159728e90a3bbd57c3)), + closes [#421](https://code.castopod.org/adaures/castopod/issues/421) + +### Features + +- **admin:** add tooltip for not authorized routes + ([f7f9baf](https://code.castopod.org/adaures/castopod/commit/f7f9bafc3e56621fab2569d9d76baafe0a2e940d)) +- **admin:** emphasize unprivileged items in sidebar with "prohibited" icon + ([0bd7dde](https://code.castopod.org/adaures/castopod/commit/0bd7ddea58adf502121b83e5c09317e20912fb4e)) +- allow hiding owner's email in public RSS feed + ([222e02a](https://code.castopod.org/adaures/castopod/commit/222e02a2af9ecb8b8768a63d3054f4c3ef54e991)) +- **persons:** order persons by full_name ASC for easier list scanning + ([68a599f](https://code.castopod.org/adaures/castopod/commit/68a599fee08c71763b9336e14b1c0d9e28c4449b)), + closes [#418](https://code.castopod.org/adaures/castopod/issues/418) + +## [1.7.4](https://code.castopod.org/adaures/castopod/compare/v1.7.3...v1.7.4) (1/3/2024) + +### Bug Fixes + +- **media:** add missing HEAD route for static assets served with S3 + ([b61a32c](https://code.castopod.org/adaures/castopod/commit/b61a32c8a9b10e129666804d533487430ce7432c)) + +## [1.7.3](https://code.castopod.org/adaures/castopod/compare/v1.7.2...v1.7.3) (12/21/2023) + +### Bug Fixes + +- **analytics:** upgrade opawg's user-agents-php to user-agents-v2-php + ([8cd7886](https://code.castopod.org/adaures/castopod/commit/8cd78866762e26aa63c224dace6c247e0e9dc068)) +- **platforms:** add Threads and YouTube Music + ([9264a2d](https://code.castopod.org/adaures/castopod/commit/9264a2d74cc95278c9d84c99ef914fdbcaf8a97f)) + +## [1.7.2](https://code.castopod.org/adaures/castopod/compare/v1.7.1...v1.7.2) (12/12/2023) + +### Bug Fixes + +- **episode-form:** render episode number optional when episode type is trailer + or bonus + ([694328f](https://code.castopod.org/adaures/castopod/commit/694328f10865b2fcd6436122de46866dae81f945)) + +## [1.7.1](https://code.castopod.org/adaures/castopod/compare/v1.7.0...v1.7.1) (12/1/2023) + +### Bug Fixes + +- **housekeeping:** add where clause to check episode_id is not null on reset + comments count + ([119742c](https://code.castopod.org/adaures/castopod/commit/119742cdbb2c2f7f847692fb76f6ff1dbb2e25b6)) + +# [1.7.0](https://code.castopod.org/adaures/castopod/compare/v1.6.5...v1.7.0) (11/29/2023) + +### Bug Fixes + +- **admin-ux:** hide navigation submenus in details panel for easier scanning + ([b047a3c](https://code.castopod.org/adaures/castopod/commit/b047a3c6707114d04c276758f2e543eef90d72f5)) +- **admin:** remove episode title truncation + display description in two lines + in episode list + ([f4ffa30](https://code.castopod.org/adaures/castopod/commit/f4ffa30ec4341f43e22b1f983781ad04c956aa25)), + closes [#386](https://code.castopod.org/adaures/castopod/issues/386) +- **auth:** display error messages from validator + ([5a834c0](https://code.castopod.org/adaures/castopod/commit/5a834c0f8957fc016e73325a3c3ff05e524d0755)) +- **housekeeping:** remove unnecessary $tablePrefix variable when resetting post + count + ([97d793f](https://code.castopod.org/adaures/castopod/commit/97d793f55e7eb3b049980e5081950baa2bb1b881)), + closes [#383](https://code.castopod.org/adaures/castopod/issues/383) +- **import:** handle bad values for location attributes + ([642981f](https://code.castopod.org/adaures/castopod/commit/642981fd358ccf118d3d7a957fb6be7933c016ac)) +- **import:** use cocur/slugify library to handle non latin text + ([4ca7f9c](https://code.castopod.org/adaures/castopod/commit/4ca7f9ccae1e352bf26a3b6db4de73bac7b84382)) +- move monetization outside of podcast form + add broadcast section to podcast + menu + ([dff8516](https://code.castopod.org/adaures/castopod/commit/dff85168b32a6df77425ef51865588ebcd8b8ba9)) +- **nodeinfo2:** import database config + use dynamic table prefix for active + local actors query + ([6a7ef01](https://code.castopod.org/adaures/castopod/commit/6a7ef0109a6e52144ca687b979ffe56fba66165b)) +- **persons:** set roles field as optional + set `Cast > Host` as default value + ([02132dc](https://code.castopod.org/adaures/castopod/commit/02132dc46640807e2bc4cfc406c911fa097f36fe)), + closes [#347](https://code.castopod.org/adaures/castopod/issues/347) +- **platforms:** make platforms' websites and submit urls more prominent + ([61cf8fa](https://code.castopod.org/adaures/castopod/commit/61cf8fa3e2435ee2a9bdd8e711b8d69d4ca4ec4c)) +- **podcast-form:** move fediverse section below author section + ([1861d67](https://code.castopod.org/adaures/castopod/commit/1861d67971e2cc0c20ace091f037f6436437a50d)) +- reorder podcast form fields + extract sync feeds to its own form + ([2d52fa1](https://code.castopod.org/adaures/castopod/commit/2d52fa1046faf1b8d81304e35fc24a7874315e6e)) + +### Features + +- **admin:** add rss feed link to podcast side navigation + ([18e2633](https://code.castopod.org/adaures/castopod/commit/18e2633a49dbbeb57a685f129a2ab158397de61e)) +- **icons:** update new Deezer logo + ([f2d5b27](https://code.castopod.org/adaures/castopod/commit/f2d5b272ac385a978d7e173121faafe03d7a7200)) +- **install:** init database and create superadmin using CLI + ([02d4ba6](https://code.castopod.org/adaures/castopod/commit/02d4ba69ac007ebd1eccab428a98b54051aaf70c)), + closes [#380](https://code.castopod.org/adaures/castopod/issues/380) +- **ux:** add episode description to episode cards + ([5f8d413](https://code.castopod.org/adaures/castopod/commit/5f8d413b84b236077a75934da9409f37d34cb4a5)) + +## [1.6.5](https://code.castopod.org/adaures/castopod/compare/v1.6.4...v1.6.5) (2023-09-26) + +### Bug Fixes + +- **fediverse:** use NoteObject including episode link in content (hotfix) + ([ffa530e](https://code.castopod.org/adaures/castopod/commit/ffa530e187ff6488648a7cf749ca0173765a5d87)) + +## [1.6.4](https://code.castopod.org/adaures/castopod/compare/v1.6.3...v1.6.4) (2023-09-17) + +### Bug Fixes + +- **fediverse:** do not cache remote action form + fix typo on post routes for + passing post uuid + ([4ecb42f](https://code.castopod.org/adaures/castopod/commit/4ecb42f7c82eb8d41d27c7b9705b3278ea04ab79)) +- **fediverse:** update post controller namespace in routes + ([3189f12](https://code.castopod.org/adaures/castopod/commit/3189f122067dc47d6de93c3185aca66d7df95e1a)) + +## [1.6.3](https://code.castopod.org/adaures/castopod/compare/v1.6.2...v1.6.3) (2023-09-14) + +### Bug Fixes + +- **fediverse:** add `index` to post controller-method to access post's jsonld + contents + ([35142d8](https://code.castopod.org/adaures/castopod/commit/35142d8e565e828a977ba2b4de77c1b47a633beb)) + +## [1.6.2](https://code.castopod.org/adaures/castopod/compare/v1.6.1...v1.6.2) (2023-09-11) + +### Bug Fixes + +- **migrations:** remove if exists modifier for drop index + ([82013c9](https://code.castopod.org/adaures/castopod/commit/82013c9cde901c54fdb3a833890aa693e8542627)), + closes [#382](https://code.castopod.org/adaures/castopod/issues/382) + +## [1.6.1](https://code.castopod.org/adaures/castopod/compare/v1.6.0...v1.6.1) (2023-09-09) + +### Bug Fixes + +- **admin:** redirect root fediverse route to fediverse-blocked-actors + ([ba5324e](https://code.castopod.org/adaures/castopod/commit/ba5324ea1942a3939f186e974d29fb393c54b253)) +- **analytics:** show full referrer domain in web pages visits reports + ([6be38e9](https://code.castopod.org/adaures/castopod/commit/6be38e9fda3d1436d81686e1a3a5e5b173e390a0)), + closes [#367](https://code.castopod.org/adaures/castopod/issues/367) +- **auth:** overwrite Shield's PermissionFilter + ([c6e8000](https://code.castopod.org/adaures/castopod/commit/c6e8000bab54f4a32068578f750f4cf9d91bad89)) +- **auth:** update shield from v1.0.0-beta.3 to v1.0.0-beta.6 + ([23842df](https://code.castopod.org/adaures/castopod/commit/23842df03ae28e416390e2436442b8e7c8340333)) +- **platforms:** add missing tiktok to social platforms seed + ([8dfdaf3](https://code.castopod.org/adaures/castopod/commit/8dfdaf321566050e9c53683e70864871eb55d618)) +- remove fediverse prefix to prevent migration error + load routes during + podcast import + ([7ff1dbe](https://code.castopod.org/adaures/castopod/commit/7ff1dbe9030768074b2fe7c7f570bfb9e7336f62)) +- **routes:** overwrite RouteCollection to include all routes + update js and + php dependencies + ([b4f1b91](https://code.castopod.org/adaures/castopod/commit/b4f1b916bfec53f071e8d0d900081c6d74486e53)) +- update Router to include latest CI changes with alternate-content logic + ([ae57601](https://code.castopod.org/adaures/castopod/commit/ae57601c838a7aa9469bae8038ac1c30d8c9a51e)) +- use podcast-activity named route instead of not existing actor route + ([3c35718](https://code.castopod.org/adaures/castopod/commit/3c357183ca51545787fcfc801b4a5829d9cd8ad6)) + +# [1.6.0](https://code.castopod.org/adaures/castopod/compare/v1.5.2...v1.6.0) (2023-08-28) + +### Bug Fixes + +- **home:** update where clause when getting all podcasts to prevent draft + podcasts from showing up + ([7a1eea5](https://code.castopod.org/adaures/castopod/commit/7a1eea58d3cbc1982baaec21d87a36e218e1910a)) +- **media:** copy and delete temp file when saving instead of moving it for FS + FileManager + ([9346e78](https://code.castopod.org/adaures/castopod/commit/9346e787bd2a2c815533092279f96ae1fe0d9aae)), + closes [#338](https://code.castopod.org/adaures/castopod/issues/338) +- **media:** get path using media_path_absolute when saving media file + ([754e7a6](https://code.castopod.org/adaures/castopod/commit/754e7a6b4b2c12cf50c1c8b166732dc3255f36fb)) +- **media:** init file properties in setAttributes' Model method + set defaults + to pathinfo data + ([0775add](https://code.castopod.org/adaures/castopod/commit/0775add67860b94a35b68c01b133ec8ec969f539)) +- **premium-podcasts:** show premium flag only when podcast has published + premium episodes + ([d10c4fd](https://code.castopod.org/adaures/castopod/commit/d10c4fd7538e6af8a5b0eb232a06522fe8c4bf8e)) +- **s3:** add a flag to serve media files by redirecting to a presigned url + instead of default proxy + ([11aa358](https://code.castopod.org/adaures/castopod/commit/11aa3586a04c166404954600235634cee77219df)) + +### Features + +- **episode:** add preview link in admin to view and share episode before + publication + ([7d21b35](https://code.castopod.org/adaures/castopod/commit/7d21b3509ec5d1aa65420efa038f44bcd235e64f)) + +## [1.5.2](https://code.castopod.org/adaures/castopod/compare/v1.5.1...v1.5.2) (2023-07-31) + +### Bug Fixes + +- **credits:** remove undefined $podcast variable from page layout + ([73a5b68](https://code.castopod.org/adaures/castopod/commit/73a5b680875cc520fd15c529c01d44df728f9be2)), + closes [#359](https://code.castopod.org/adaures/castopod/issues/359) +- **platforms:** change twitter to X + add buymeacoffee and kofi as funding + ([d69b4e4](https://code.castopod.org/adaures/castopod/commit/d69b4e4857fcb1ac1c05ac59c78d130788f00400)), + closes [#353](https://code.castopod.org/adaures/castopod/issues/353) + [#361](https://code.castopod.org/adaures/castopod/issues/361) + +## [1.5.1](https://code.castopod.org/adaures/castopod/compare/v1.5.0...v1.5.1) (2023-07-29) + +### Bug Fixes + +- **admin-ui:** remove button labels on smaller screens in podcast view + ([9cc5ffd](https://code.castopod.org/adaures/castopod/commit/9cc5ffd1439fdc86f46a03f4319cae32db95f84e)) +- **rss:** set srt transcripts' mimetype to application/x-subrip with + rel="captions" attribute + ([16a3fdb](https://code.castopod.org/adaures/castopod/commit/16a3fdb56e3f07185e75d106216f29519ccb25f7)), + closes [#360](https://code.castopod.org/adaures/castopod/issues/360) +- **rss:** update podcast extension namespace + ([6833dd0](https://code.castopod.org/adaures/castopod/commit/6833dd05ab51bc530d34fd4174ad732f623226c0)), + closes [#360](https://code.castopod.org/adaures/castopod/issues/360) + +# [1.5.0](https://code.castopod.org/adaures/castopod/compare/v1.4.7...v1.5.0) (2023-07-27) + +### Bug Fixes + +- **admin-ui:** truncate header title + remove sticky podcast banner card on + mobile + ([63c20da](https://code.castopod.org/adaures/castopod/commit/63c20da5ffd500265f06fa38f2b2c963e14602af)) + +### Features + +- add podcast links page including social, podcasting and funding links + ([8ae2929](https://code.castopod.org/adaures/castopod/commit/8ae292933af15fa99856582ac24e985bfef37d5b)) + +## [1.4.7](https://code.castopod.org/adaures/castopod/compare/v1.4.6...v1.4.7) (2023-07-19) + +### Bug Fixes + +- **s3:** allow CORS for served static files + ([9b955c9](https://code.castopod.org/adaures/castopod/commit/9b955c9ce25a06a9102b67ebe77375dc45d28f0f)) + +## [1.4.6](https://code.castopod.org/adaures/castopod/compare/v1.4.5...v1.4.6) (2023-07-11) + +### Bug Fixes + +- **fediverse:** expand object before sending accept follow request + ([082cdc9](https://code.castopod.org/adaures/castopod/commit/082cdc9ee79d004c2ed748e3b8046e9141bf0242)), + closes [#350](https://code.castopod.org/adaures/castopod/issues/350) +- **podcast-import:** remove error log when no import in queue, exit with + success instead + ([5e719f3](https://code.castopod.org/adaures/castopod/commit/5e719f3e9eb6cf48c3fd8ac97181638b24d03fc9)) + +## [1.4.5](https://code.castopod.org/adaures/castopod/compare/v1.4.4...v1.4.5) (2023-07-04) + +### Bug Fixes + +- **s3:** handle range requests to serve media files + ([41a5932](https://code.castopod.org/adaures/castopod/commit/41a59322332c835808a32987aaf8ec6cafbf5fca)) + +## [1.4.4](https://code.castopod.org/adaures/castopod/compare/v1.4.3...v1.4.4) (2023-07-02) + +### Bug Fixes + +- **audio-clipper:** init segment position on firstUpdate + improve UX by adding + ghost handle + ([aa68386](https://code.castopod.org/adaures/castopod/commit/aa683866671d14c0b9a11b09c74eb132673e5547)), + closes [#351](https://code.castopod.org/adaures/castopod/issues/351) +- set resized images to 72dpi for compatibility with Apple Podcasts + ([0b327cb](https://code.castopod.org/adaures/castopod/commit/0b327cb4d9c92d0ae227a0f08ede3b29390df172)), + closes [#282](https://code.castopod.org/adaures/castopod/issues/282) + +## [1.4.3](https://code.castopod.org/adaures/castopod/compare/v1.4.2...v1.4.3) (2023-06-29) + +### Bug Fixes + +- **video-clipper:** add -t option to ffmpeg command to stop generation after + duration + ([60814b8](https://code.castopod.org/adaures/castopod/commit/60814b8d202419c2bdbf6abb7c2bde447537b7e9)), + closes [#341](https://code.castopod.org/adaures/castopod/issues/341) + +## [1.4.2](https://code.castopod.org/adaures/castopod/compare/v1.4.1...v1.4.2) (2023-06-27) + +### Bug Fixes + +- **fediverse:** check that actor's images mimetype is present or guess it + otherwise + ([06c4f15](https://code.castopod.org/adaures/castopod/commit/06c4f15477a568407a3d3c1e5e489bc0241bc1e9)), + closes [#348](https://code.castopod.org/adaures/castopod/issues/348) +- **podcast-import:** show cancel or retry action depending on task status + ([e42258d](https://code.castopod.org/adaures/castopod/commit/e42258de1f331aac0cbb380b80cd8fc7f9d7dc18)) + +## [1.4.1](https://code.castopod.org/adaures/castopod/compare/v1.4.0...v1.4.1) (2023-06-22) + +### Bug Fixes + +- **podcast-import:** set default values for person group and role if not found + in taxonomy + ([aa46dca](https://code.castopod.org/adaures/castopod/commit/aa46dca4e399bf2e544d62dcb4a9a0328e4e6c41)) + +# [1.4.0](https://code.castopod.org/adaures/castopod/compare/v1.3.5...v1.4.0) (2023-06-21) + +### Bug Fixes + +- **charts:** set duration charts label to HHhMM for listening time analytics + ([3fc1d8e](https://code.castopod.org/adaures/castopod/commit/3fc1d8e18dc8119251c72dcaa7e5121246c2b194)) +- **embed:** set height of player iframe from config + ([4665741](https://code.castopod.org/adaures/castopod/commit/4665741425532f253a46a42ba05602047798dba2)) +- **s3:** serve files without cache if dummy cache handler + add http referer + header to redirect + ([30db9f0](https://code.castopod.org/adaures/castopod/commit/30db9f0667bf7f7a5f186ea667a524d1e3b502db)) +- **s3:** use presigned request uri to serve static files + ([cb92dc7](https://code.castopod.org/adaures/castopod/commit/cb92dc73f17543d32d1cdc24db72403a5c561a74)) +- **webmanifest:** import misc helper to get site_icon_url + ([548a11d](https://code.castopod.org/adaures/castopod/commit/548a11d501749fa61ef894fd8818abae5668554f)) + +### Features + +- **import:** run podcast imports' processes asynchronously using tasks + ([d8e1d40](https://code.castopod.org/adaures/castopod/commit/d8e1d4031d86de9a3889b74ae2a6d9c90af8a1da)) +- **rest-api:** add endpoints for episodes and full text search for podcasts and + episodes + ([85505d4](https://code.castopod.org/adaures/castopod/commit/85505d4b3181c96bc91619e3ab9b0601f8e1c120)), + closes [#296](https://code.castopod.org/adaures/castopod/issues/296) + +## [1.3.5](https://code.castopod.org/adaures/castopod/compare/v1.3.4...v1.3.5) (2023-05-09) + +### Bug Fixes + +- replace essence with embera to create preview cards + ([c682f03](https://code.castopod.org/adaures/castopod/commit/c682f03a67c6c0ebbcc6ff45d9a037f6f9823bde)) + +## [1.3.4](https://code.castopod.org/adaures/castopod/compare/v1.3.3...v1.3.4) (2023-05-05) + +### Bug Fixes + +- **import-update:** insert episodes incrementally into database + ([108fdf8](https://code.castopod.org/adaures/castopod/commit/108fdf84b8dd458fc71a06a77d14069287ab8e42)) + +## [1.3.3](https://code.castopod.org/adaures/castopod/compare/v1.3.2...v1.3.3) (2023-04-17) + +### Bug Fixes + +- unnescape podcast title special characters in "find us on" section + ([f727276](https://code.castopod.org/adaures/castopod/commit/f727276f820a8ef2c47947f40a37a4a157b509ef)), + closes [#323](https://code.castopod.org/adaures/castopod/issues/323) +- **websub:** add missing misc helper import + ([855aacc](https://code.castopod.org/adaures/castopod/commit/855aacce0bf3841a876cd593e668e116149080aa)) + +## [1.3.2](https://code.castopod.org/adaures/castopod/compare/v1.3.1...v1.3.2) (2023-04-14) + +### Bug Fixes + +- remove path key when getting default avatar path + ([c5a1359](https://code.castopod.org/adaures/castopod/commit/c5a1359218d61c0f78006f2bd5785e317f32bade)) +- **s3:** serve files using media base url to allow for CDN setup + ([502f53c](https://code.castopod.org/adaures/castopod/commit/502f53c9701da3b8da2caef1eb54df25b7d2d86a)) + +## [1.3.1](https://code.castopod.org/adaures/castopod/compare/v1.3.0...v1.3.1) (2023-04-13) + +### Bug Fixes + +- **s3:** add proxy to serve images from s3 to client + ([a76724a](https://code.castopod.org/adaures/castopod/commit/a76724a8cfee700f6874f86b35616d61facc664e)), + closes [#321](https://code.castopod.org/adaures/castopod/issues/321) + +# [1.3.0](https://code.castopod.org/adaures/castopod/compare/v1.2.4...v1.3.0) (2023-04-03) + +### Bug Fixes + +- delete files using file_manager when deleting episode and podcast + ([41d8efe](https://code.castopod.org/adaures/castopod/commit/41d8efe6e71566eba44bfdfd00d1708ac4338366)) + +### Features + +- **media:** set media storage directory as configurable + ([7e1a470](https://code.castopod.org/adaures/castopod/commit/7e1a470ba42172eb4c3864ab3652e9f8b55d1ba8)) + +## [1.2.4](https://code.castopod.org/adaures/castopod/compare/v1.2.3...v1.2.4) (2023-03-23) + +### Bug Fixes + +- allow images to have .jpeg extension consistently + ([ae5e12b](https://code.castopod.org/adaures/castopod/commit/ae5e12be3b15fe50cb2311abcbbc19ac23b592f6)) +- **s3:** delete persons image sizes from bucket + add keyPrefix to config + ([208c271](https://code.castopod.org/adaures/castopod/commit/208c2715f900371987c3b75a749fe937a3db1991)) +- **s3:** do not create bucket if not exists, check if healthy instead + ([da7076f](https://code.castopod.org/adaures/castopod/commit/da7076fc2d49d07708d5adaa99733487b7f52e20)) + +### Reverts + +- **homepage:** remove redirect to install if database is not setup + ([d4954e0](https://code.castopod.org/adaures/castopod/commit/d4954e026d5e0d48c5f15ed69d1ce71abb34d1a1)) + +## [1.2.3](https://code.castopod.org/adaures/castopod/compare/v1.2.2...v1.2.3) (2023-03-18) + +### Bug Fixes + +- **notifications:** set mark-all-as-read parameter to be podcast_id instead of + actor_id + ([2748f23](https://code.castopod.org/adaures/castopod/commit/2748f2313797e50d8a2a7b87df09c0bc6e64360a)) + +## [1.2.2](https://code.castopod.org/adaures/castopod/compare/v1.2.1...v1.2.2) (2023-03-18) + +### Bug Fixes + +- **migration:** change old media file_key to file_path + ([a414142](https://code.castopod.org/adaures/castopod/commit/a4141421aa1d6e89742b390b042382f729f965a9)), + closes [#314](https://code.castopod.org/adaures/castopod/issues/314) + +## [1.2.1](https://code.castopod.org/adaures/castopod/compare/v1.2.0...v1.2.1) (2023-03-17) + +### Bug Fixes + +- change app.mediaBaseURL to media.baseURL in install, docker entrypoints and + docs + ([b3c6e05](https://code.castopod.org/adaures/castopod/commit/b3c6e05e6fcd8a518eeedeefde28b61f879ba71d)) + +# [1.2.0](https://code.castopod.org/adaures/castopod/compare/v1.1.2...v1.2.0) (2023-03-17) + +### Bug Fixes + +- **analytics:** check the x_forwarded_for client header + ([1111177](https://code.castopod.org/adaures/castopod/commit/1111177eb7fea4eba6d119b17acdf3bf416492ef)) +- **auth:** update podcast editors' permissions + ([a9b6308](https://code.castopod.org/adaures/castopod/commit/a9b630884bc318499ea7f03862d5752dd5f178e1)) +- **contributors:** add dash to prevent deleting permissions from other podcast + ([5d2a2d4](https://code.castopod.org/adaures/castopod/commit/5d2a2d49c489cd98f9c9ecbca35fd5d21a9cadfb)), + closes [#310](https://code.castopod.org/adaures/castopod/issues/310) +- display bandwidth limit on dashboard when set in .env + ([a2a87ab](https://code.castopod.org/adaures/castopod/commit/a2a87abf7caea3c87bcf2d0988610cc07782de9e)) +- **docker:** update nginx configuration + ([8884598](https://code.castopod.org/adaures/castopod/commit/8884598a56d0e2550776ef4cee5e53558c20e009)) +- **platforms:** update 'submit_url' for Antennapod + ([9fc49a7](https://code.castopod.org/adaures/castopod/commit/9fc49a7430406f50e68318c5fd7c577ae1ebd9df)) + +### Features + +- add downloads count to episode list + ([b63c1dc](https://code.castopod.org/adaures/castopod/commit/b63c1dc9b1ed41626b99ba852a9a00ed417059ba)) +- add health route to check if db, cache and file manager are ok + ([1dde11f](https://code.castopod.org/adaures/castopod/commit/1dde11f8e42b66684a956068f5347e9289f4918b)) +- **media:** add s3 to manage media files + ([d93fc98](https://code.castopod.org/adaures/castopod/commit/d93fc98469ffe93913b65e539dec396891708c70)) + +### Reverts + +- **install:** reset condition to look for instance owner before continuing + install + ([fc009f3](https://code.castopod.org/adaures/castopod/commit/fc009f3d0058028bbbb6418603cf820c0f7cea80)) + +## [1.1.2](https://code.castopod.org/adaures/castopod/compare/v1.1.1...v1.1.2) (2022-12-14) + +### Bug Fixes + +- **analytics:** set EpisodeAudioController to init user session data + ([77ccb30](https://code.castopod.org/adaures/castopod/commit/77ccb306009eb093147c56789535e754f3d85570)) + +## [1.1.1](https://code.castopod.org/adaures/castopod/compare/v1.1.0...v1.1.1) (2022-12-13) + +### Bug Fixes + +- **op3:** remove scheme when wraping audio URI + ([0ad22e4](https://code.castopod.org/adaures/castopod/commit/0ad22e49bc488e96df5a41495f5b242559b64a45)) +- **rss:** add file extension to enclosure url + ([964cbba](https://code.castopod.org/adaures/castopod/commit/964cbba54f16556408bf8280c544a52e6be5c9fc)) + +# [1.1.0](https://code.castopod.org/adaures/castopod/compare/v1.0.5...v1.1.0) (2022-12-09) + +### Bug Fixes + +- **notifications:** remove cache inconsistencies when marking notification as + read + ([46d7054](https://code.castopod.org/adaures/castopod/commit/46d70541d313c836ab0c078ba6121fe5fe956e62)) +- **notifications:** retrieve activity from database instead of getting cache + ([7fbbd08](https://code.castopod.org/adaures/castopod/commit/7fbbd08da6a37d08608900ad318e72815fe4b0c4)) +- **podcast:soundbite:** rename start time attribute to follow spec + ([689831c](https://code.castopod.org/adaures/castopod/commit/689831c26c756d454de432900d23bc09a37f890b)) + +### Features + +- **analytics:** add OP3 analytics service option + update episode audio url + ([16527ed](https://code.castopod.org/adaures/castopod/commit/16527ed529265f2925e205856c684e34175a8933)) + +## [1.0.5](https://code.castopod.org/adaures/castopod/compare/v1.0.4...v1.0.5) (2022-11-25) + +### Bug Fixes + +- **router:** revert to CI4 v4.2.7 to include all routes + ([c13cfa0](https://code.castopod.org/adaures/castopod/commit/c13cfa0ea0679751521ca4157b953043ecc7974a)) + +## [1.0.4](https://code.castopod.org/adaures/castopod/compare/v1.0.3...v1.0.4) (2022-11-21) + +### Bug Fixes + +- update actorUsername regex to get url_to actor + ([1d6b177](https://code.castopod.org/adaures/castopod/commit/1d6b177a55111ede01fba1c08499036d474533bc)) + +## [1.0.3](https://code.castopod.org/adaures/castopod/compare/v1.0.2...v1.0.3) (2022-11-17) + +### Bug Fixes + +- **dashboard-ui:** fill the blank gaps between cards on smaller screen sizes + ([00836cc](https://code.castopod.org/adaures/castopod/commit/00836cc368c75ae2e23fa5dc4a53a5bb6eb2ce24)) + +## [1.0.2](https://code.castopod.org/adaures/castopod/compare/v1.0.1...v1.0.2) (2022-11-04) + +### Bug Fixes + +- **auth:** disallow registration by default + ([379b9be](https://code.castopod.org/adaures/castopod/commit/379b9be2b99574fe4af4009b01128dba2c75f037)) +- **contributors:** add prefix to podcast group to delete contributor + ([9f785db](https://code.castopod.org/adaures/castopod/commit/9f785db7ba674638a6f456aa3626f3f8100911f1)) +- extract podcast ids from user groups using a regex + ([e26215a](https://code.castopod.org/adaures/castopod/commit/e26215a11fc23aa0ad5ccff8ee97d6c6e8a09c1a)) +- **notifications:** add manage-notifications permission to podcast + ([ed7c247](https://code.castopod.org/adaures/castopod/commit/ed7c247bcbbb450e5ff96418930d3b37ce912cc4)) +- **platforms:** convert special characters to htmlentities to validate url + ([82310a2](https://code.castopod.org/adaures/castopod/commit/82310a2e0b426e84501090bdd9c0cf592d1c0d53)) + +## [1.0.1](https://code.castopod.org/adaures/castopod/compare/v1.0.0...v1.0.1) (2022-11-01) + +### Bug Fixes + +- **platforms:** trim platform url before validation and storage + ([259fe5f](https://code.castopod.org/adaures/castopod/commit/259fe5f697a833e268cde88e959bc19bd662edf6)) + +# 1.0.0 (2022-10-20) + +### Bug Fixes + +- **a11y:** replace active tab color to contrast with background on podcast and + episode pages + ([f3785e1](https://code.castopod.org/adaures/castopod/commit/f3785e140147d085a2fb6a62ded87cdfe360f442)) +- **activity-pub:** cache issues when navigating to activity stream urls + ([7bcbfb3](https://code.castopod.org/adaures/castopod/commit/7bcbfb32f7cca08d111be46c7f1640e372d4a4b0)) +- **activity-pub:** get database records using new model instances + ([92536dd](https://code.castopod.org/adaures/castopod/commit/92536ddb3812214a9c5682b92e547e5c1998a5d7)) +- **activitypub:** add conditions for possibly missing actor properties + add + user-agent to requests + ([8fbf948](https://code.castopod.org/adaures/castopod/commit/8fbf948fbba22ffd33966a1b2ccd42e8f7c1f8a2)) +- **activitypub:** add target actor id to like / announce activities to send + directly to note's actor + ([962dd30](https://code.castopod.org/adaures/castopod/commit/962dd305f5d3f6eadc68f400e0e8f953827fe20d)) +- **activitypub:** add target_actor_id for create activity to broadcast post + reply + ([0128a21](https://code.castopod.org/adaures/castopod/commit/0128a21ec55dcc0a2fbf4081dadb4c4737735ba1)) +- **activitypub:** allow cors on get requests for routes exposing acitivitypub + objects + ([2f24809](https://code.castopod.org/adaures/castopod/commit/2f2480998f9abb34f02ab186c65d462a74b4e640)) +- **activitypub:** set created_by to null for reblog if no user + update episode + oembed data + ([209dfbd](https://code.castopod.org/adaures/castopod/commit/209dfbd134e1a2cc02e7c24c158d786fa4dda61d)) +- add admin-audio-player to vite config to have admin player show up + ([93cb9b2](https://code.castopod.org/adaures/castopod/commit/93cb9b24701c09b92820204a67c1fc1b3c044708)) +- add application/octet-stream mimetype to mp3 and m4a extensions to prevent + ext_in error + ([339bef8](https://code.castopod.org/adaures/castopod/commit/339bef878e54983d86e91e6ff7a931a843d321b3)), + closes [#145](https://code.castopod.org/adaures/castopod/issues/145) +- add category_label component to include parent category in about podcast page + ([74e7d68](https://code.castopod.org/adaures/castopod/commit/74e7d68ac834885c4b89ee6e7d60db2157165799)) +- add explicit int conversion when formatting episode duration + ([1253096](https://code.castopod.org/adaures/castopod/commit/1253096197a0d30692bdafa7152f250cd9a71acf)) +- add head request to analytics_hit route + ([f0a2f0b](https://code.castopod.org/adaures/castopod/commit/f0a2f0bea491ca91976b351bb79837e95c9d094b)) +- add href to castopod website on login page + ([cc54257](https://code.castopod.org/adaures/castopod/commit/cc5425735184ad738aa0f38540f18e8971f8f56e)) +- add missing explicit badge for podcasts and episodes + ([cdf9f9d](https://code.castopod.org/adaures/castopod/commit/cdf9f9d53f2597f19455cb65c51da4677bb99327)) +- add open graph size for podcast images to replace the inadequate large format + ([33aae1f](https://code.castopod.org/adaures/castopod/commit/33aae1f7934e4962116e94e477dbf48e24971f5f)) +- add public/media folder to castopod bundle + ([8053d35](https://code.castopod.org/adaures/castopod/commit/8053d3521b481872711dabaaf265d08b9bfbaa87)), + closes [#52](https://code.castopod.org/adaures/castopod/issues/52) +- add translation key for audio-clipper trim labels + ([db191ac](https://code.castopod.org/adaures/castopod/commit/db191ac31bd16bad2a72afdb8b25c685adf86a6e)) +- add underline and semibold font weight for prose links to have them stand out + ([d4d8671](https://code.castopod.org/adaures/castopod/commit/d4d867121c50bded4176a53d7154cf1bb347e306)) +- add where condition to get episode count without deleted episodes + ([7661734](https://code.castopod.org/adaures/castopod/commit/7661734ed296654630f3668132671117519145dd)), + closes [#67](https://code.castopod.org/adaures/castopod/issues/67) +- **admin:** save block and lock switches + ([b66c0af](https://code.castopod.org/adaures/castopod/commit/b66c0afc8fab2e338402a9a4f8105e5f5459e208)) +- **analytics:** redirect to mp3 file even when referer was not set + ([9fc388d](https://code.castopod.org/adaures/castopod/commit/9fc388d154f29c335dedcd624abe8c1751762c07)) +- **analytics:** remove charts empty values + remove useless language cache + ([1678794](https://code.castopod.org/adaures/castopod/commit/16787941539ba4014281a366789ea896a9cd2afc)) +- **analytics:** set duration field to precise decimal as episode's audio file + duration + ([d772685](https://code.castopod.org/adaures/castopod/commit/d77268540569b2be9d91d5e09aefb3ff5ac2b071)) +- **analytics:** set initial value for duration and bandwidth + ([ee50539](https://code.castopod.org/adaures/castopod/commit/ee5053959154b1a2e5fbe4b43162968425206a26)) +- **analytics:** update migrations to set decimal precision for latitude and + longitude + ([714d6b5](https://code.castopod.org/adaures/castopod/commit/714d6b5d4950e52cf1c3170bb59954f98ffd48bd)) +- **analytics:** update service management so that it works with new OPAWG slug + values + ([7fe9d42](https://code.castopod.org/adaures/castopod/commit/7fe9d42500ade2c6fa3ff4365b4affc475af0e51)) +- **audio-clipper:** add mouse position offset when stretching clip to prevent + content from jumping + ([602654b](https://code.castopod.org/adaures/castopod/commit/602654b99b33ee8c29da080058a0aaea976cd484)) +- **audio-clipper:** show audio playing progress + put waveform behind audio + clipper + ([01a09dc](https://code.castopod.org/adaures/castopod/commit/01a09dc447b81c5412ceb45d6706a867939fd4dd)) +- **avatar:** use default avatar when no avatar url has been set + ([9d23c7e](https://code.castopod.org/adaures/castopod/commit/9d23c7e7e142c6cf1a1418e37e41d711064593c4)), + closes [#111](https://code.castopod.org/adaures/castopod/issues/111) +- **bundle:** include modules and themes when copying files with rsync + ([cd5bb88](https://code.castopod.org/adaures/castopod/commit/cd5bb8835c6e259408a8c13a2196a347e161da83)) +- **bundle:** update vite input files path + add `set -e` in bash scripts to + fail if command fails + ([0ee53c7](https://code.castopod.org/adaures/castopod/commit/0ee53c71ffadb8a6ddb1febd9f912bc99f5f7a0b)) +- **cache:** add locale for podcast and episode pages + clear some persisting + cache in models + ([9cec8a8](https://code.castopod.org/adaures/castopod/commit/9cec8a81ccbb7239402fe6633dbc31979272302a)), + closes [#42](https://code.castopod.org/adaures/castopod/issues/42) + [#61](https://code.castopod.org/adaures/castopod/issues/61) +- **cache:** delete posts and comments pages cache when updating platform links + ([f7c3e5b](https://code.castopod.org/adaures/castopod/commit/f7c3e5bf4ad43389bf8d58d2c4aaf16b81cbce00)), + closes [#169](https://code.castopod.org/adaures/castopod/issues/169) +- **cache:** return a non cached view when connected + ([e2e7358](https://code.castopod.org/adaures/castopod/commit/e2e735815d805a48eed2ea3288d060d0ddb253a3)) +- **cache:** suffix cache names with authenticated for credits, map and pages + ([418a70b](https://code.castopod.org/adaures/castopod/commit/418a70b2a670d8ba0ab6c15fa5faa41f6be55e53)) +- cast actor_id to pass as int to set_interact_as_actor() function + ([56a8e5d](https://code.castopod.org/adaures/castopod/commit/56a8e5d7dd615322aeb007e730801c65d0b02e5c)) +- **category:** remove uncategorized option to enforce users in choosing a + category + ([8c64f25](https://code.castopod.org/adaures/castopod/commit/8c64f25a0e72fec03d25544797d32623b2276fce)) +- change image size requirement hints + ([ea20206](https://code.castopod.org/adaures/castopod/commit/ea20206ee674eb54dd3ea188d2a2e2d41425df65)) +- change message upon cancellation of episode publication + ([9859c74](https://code.castopod.org/adaures/castopod/commit/9859c7434c2a3478ce035f7a4de20f594d63f5b0)) +- check for database connection and podcasts table existence before redirecting + to install + ([eb74e81](https://code.castopod.org/adaures/castopod/commit/eb74e81c3d93581e310b391cd029e62a0d690a8a)) +- check that additional files are valid when creating episode + ([eac5bc8](https://code.castopod.org/adaures/castopod/commit/eac5bc876de125e1fe08d1b89f767a04fc0fbfb6)) +- check that note has a preview_card_id before displaying it + ([acb8b3a](https://code.castopod.org/adaures/castopod/commit/acb8b3a40172ccb184ffe544760601d756692e6c)), + closes [#114](https://code.castopod.org/adaures/castopod/issues/114) +- clear cache when deleting podcast banner + ([99bb40b](https://code.castopod.org/adaures/castopod/commit/99bb40b8bc17b8ee2cd8468a82e46ea280c92cb6)) +- comment all cache clean after page update to prevent analytics cache deletion + ([e6197a4](https://code.castopod.org/adaures/castopod/commit/e6197a4972a3cce3d67dd7972bb54f8720b8e5b7)) +- **comments:** add comment view partials for public pages + ([fcecbe1](https://code.castopod.org/adaures/castopod/commit/fcecbe1c68b0d28d19454fba65caf3ab769fbc75)) +- correct chart data + ([4d3e9c8](https://code.castopod.org/adaures/castopod/commit/4d3e9c8c02cdc882e9fe1c29625695b6f83c820a)) +- correct percona compatibility issue + ([e53f819](https://code.castopod.org/adaures/castopod/commit/e53f819264b2d6902996f11ffcbb7c99295a90ef)) +- correct php-fpm issues + ([1ef55d7](https://code.castopod.org/adaures/castopod/commit/1ef55d7315bb44abe05f02ec8a84b6b6a557a9a0)) +- correct referrer bug + ([ed69b2f](https://code.castopod.org/adaures/castopod/commit/ed69b2f5004ed1cd18bac824c08a0df01f5d2637)) +- correction for servers with low int precision + ([31b7828](https://code.castopod.org/adaures/castopod/commit/31b7828e77519ef43e9bcfcbdf6c21712f97a571)) +- **cors:** add preflight option routes for episode, podcast and status objects + ([a281abf](https://code.castopod.org/adaures/castopod/commit/a281abfda475388a07943c169dab460cc2d4f944)) +- declare typed properties in PHPDoc for php<7.4 + ([14dd44d](https://code.castopod.org/adaures/castopod/commit/14dd44d03d6db0d9ae4198db8e65c92a0e45cb31)), + closes [#23](https://code.castopod.org/adaures/castopod/issues/23) +- define podcast_id and platform_slug as foreign keys in podcasts_plaforms table + ([6e9451a](https://code.castopod.org/adaures/castopod/commit/6e9451a1103b43750fa70ad576de36af25ca29cb)) +- define podcastNamespaceLink value + ([0d744d2](https://code.castopod.org/adaures/castopod/commit/0d744d212df0d070ceea185068eaf2746e1ccd48)) +- **email:** set the correct url in the activation and forgot emails + ([10fc6f1](https://code.castopod.org/adaures/castopod/commit/10fc6f17c6838a58348f32ccfd0cf05f9d3e172c)), + closes [#204](https://code.castopod.org/adaures/castopod/issues/204) +- **embeddable-player:** enable any ancestor when X-Frame-Options is set on + server + ([44a4962](https://code.castopod.org/adaures/castopod/commit/44a4962e0b7e3ed87e9914b4e7792a0d52330ff8)) +- **embed:** open embedded player's links in new tab + ([4aa73d7](https://code.castopod.org/adaures/castopod/commit/4aa73d71e3b8c0a6c3f75f4d1d45c4d693aba64c)) +- **episode-form:** show warning to set `memory_limit`, `upload_max_filesize` & + `post_max_size` + ([3b3c218](https://code.castopod.org/adaures/castopod/commit/3b3c218b9c868e9f12c54d7670e69d84c9ee79c0)), + closes [#5](https://code.castopod.org/adaures/castopod/issues/5) + [#86](https://code.castopod.org/adaures/castopod/issues/86) +- **episode-unpublish:** set consistent posts_counts' increments/decrements for + actors and episodes + ([8acdafd](https://code.castopod.org/adaures/castopod/commit/8acdafd26044e50a4d6ee451bf24ad66003c5bb3)), + closes [#233](https://code.castopod.org/adaures/castopod/issues/233) +- **episodeCount:** add missing brackets to French language file + ([c1b4112](https://code.castopod.org/adaures/castopod/commit/c1b411265ad9b06e95a8b097ecf73445b88dcb45)) +- **episode:** replace guid's empty string value to null + ([441052a](https://code.castopod.org/adaures/castopod/commit/441052af8d99e6e317edefd1e58ad71799357088)) +- **episodes-page:** handle defaultQuery being null when no podcast episodes + ([15183b7](https://code.castopod.org/adaures/castopod/commit/15183b7eab57dac007bcdfa8c3651239de1ae05a)), + closes [#100](https://code.castopod.org/adaures/castopod/issues/100) +- **episodes-table:** set descriptions to be not null + ([6774ec1](https://code.castopod.org/adaures/castopod/commit/6774ec10fa78527be6b7548ca1dc34ad0ada090c)) +- **episodes:** add publication status + set publication date to null when none + has been set + ([d882981](https://code.castopod.org/adaures/castopod/commit/d882981b3a86c81921ce6b07d4cf61fc13983689)), + closes [#70](https://code.castopod.org/adaures/castopod/issues/70) +- escape characters for `min` in format_duration_symbol + ([3b6722a](https://code.castopod.org/adaures/castopod/commit/3b6722a42b9e4330e5235d4ceed41c777159f4dc)) +- escape generated feed tag values and remove new lines from public pages meta + description + ([6238a43](https://code.castopod.org/adaures/castopod/commit/6238a43863210afe8988ad7cf251e6bfc6c8557c)), + closes [#57](https://code.castopod.org/adaures/castopod/issues/57) + [#46](https://code.castopod.org/adaures/castopod/issues/46) +- expire default query cache upon scheduled episode publication + ([b72e7c8](https://code.castopod.org/adaures/castopod/commit/b72e7c8691c887e41107baea0a4d50a39eaf8c8b)), + closes [#81](https://code.castopod.org/adaures/castopod/issues/81) +- explicitly cast seconds to int in iso8601_duration helper function + ([779653f](https://code.castopod.org/adaures/castopod/commit/779653f75b140942f731cbb238bc0667cc461307)) +- **fediverse:** set default castopod avatar url when actor avatar is not + present + ([460f52f](https://code.castopod.org/adaures/castopod/commit/460f52f70e493d619c28632db6c698e88f0ebb5f)) +- **fediverse:** set model instances as non shared to prevent overlapping + ([91128fa](https://code.castopod.org/adaures/castopod/commit/91128fad7a68e1f4e5acacba90b6899288699e61)) +- fix layout bugs in admin and update translation files + ([a834171](https://code.castopod.org/adaures/castopod/commit/a83417180cf61cdfadc5509b0aaa2fdb66592be3)), + closes [#40](https://code.castopod.org/adaures/castopod/issues/40) +- **follow:** add missing helpers to Actor controller + ([ee53a73](https://code.castopod.org/adaures/castopod/commit/ee53a732dc12ebbf5706e14969749a12cfd9d559)) +- **get_browser_language:** return defaultLocale if browser doesn't send user + preferred language + ([9cc2996](https://code.castopod.org/adaures/castopod/commit/9cc299626181048b85b629bbe7f5806a1f5d21ff)) +- handle HEAD requests on podcast_feed route + ([74b2640](https://code.castopod.org/adaures/castopod/commit/74b2640f2a25c4cd6fd8835fc492c2a6893d4950)), + closes [#79](https://code.castopod.org/adaures/castopod/issues/79) +- **home:** remove hardcoded prefix in getAllPodcasts query + ([92d5cc5](https://code.castopod.org/adaures/castopod/commit/92d5cc50a3e533875cd894dccc417918102d4b7f)) +- **housekeeping:** replace the use of GLOB_BRACE with looping over file + extensions + ([42d92d0](https://code.castopod.org/adaures/castopod/commit/42d92d0c8dfe0c567c28f5bfdda129890fa4c2ec)), + closes [#154](https://code.castopod.org/adaures/castopod/issues/154) +- **housekeeping:** set default sizes value + ignore illegal IFD size error to + proceed with script + ([f21ca57](https://code.castopod.org/adaures/castopod/commit/f21ca57603cfa503699b7e09a155e18d876d65fe)) +- **housekeeping:** use EpisodeModel's builder to reset comments count + ([65e9c0b](https://code.castopod.org/adaures/castopod/commit/65e9c0b05ea4992884149cb4a4b071bf31a20a1a)) +- **htaccess:** add ? after index.php in RewriteRule + ([d9d139e](https://code.castopod.org/adaures/castopod/commit/d9d139eefa03c28d1a064b3b32c9036193497e57)), + closes [#152](https://code.castopod.org/adaures/castopod/issues/152) +- **http-signature:** update SIGNATURE_PATTERN allowing signature keys to be + sent in any order + ([b7f285e](https://code.castopod.org/adaures/castopod/commit/b7f285e4e24247fedb94f030356fa6f291f525cc)) +- **images:** set default mimetype if none is specified when getting size info + ([6e4acc6](https://code.castopod.org/adaures/castopod/commit/6e4acc64ad256178cee7905402b48bafcd49f84c)) +- **import-with-escaped-characters:** remove \CodeIgniter\HTTP\URI in + download_file, closes + [#103](https://code.castopod.org/adaures/castopod/issues/103) + ([35b5be0](https://code.castopod.org/adaures/castopod/commit/35b5be095ff54d27acec1610a846ec0cdbdf1d65)) +- **import:** add extension when downloading file without + truncate slug if too + long + ([c5f18bb](https://code.castopod.org/adaures/castopod/commit/c5f18bb6dc08a758ff735454bbe9cfa45a68c09b)) +- **import:** add validation for handle field to prevent + Router.invalidParameterType error + ([5bf7200](https://code.castopod.org/adaures/castopod/commit/5bf7200fb390f2447b29f24b495f24483cf7b205)), + closes [#119](https://code.castopod.org/adaures/castopod/issues/119) +- **import:** cast description's SimpleXMLElement to string + ([02d17be](https://code.castopod.org/adaures/castopod/commit/02d17be4ffe229fc6657207d31eba0543b5f1a4c)) +- **import:** remove query string from files url + ([109c4aa](https://code.castopod.org/adaures/castopod/commit/109c4aa1afb72dd8b99c0302d74a7fef5a38638e)) +- **import:** save media files during podcast import + set missing media fields + ([a9989d8](https://code.castopod.org/adaures/castopod/commit/a9989d841a634f8cf6c04df25f40bb1e7d4fcdcc)) +- **import:** set default episode type if not set + ([d7250ab](https://code.castopod.org/adaures/castopod/commit/d7250ab03f9b032830c575ad58b51c8d60b7a49a)) +- **import:** set episode and season numbers to null when not present in item + tag + ([3211398](https://code.castopod.org/adaures/castopod/commit/3211398c78b1b28b76a46427ee07874bbf84a85d)) +- **import:** use tag when no is present + ([20e607a](https://code.castopod.org/adaures/castopod/commit/20e607afb755bc75056041738fa7cbf6723d754c)) +- include missing variables on public ui's episode page and remote_actions + ([193b373](https://code.castopod.org/adaures/castopod/commit/193b373bc94a5270acae99b637aa84b6cb2dedfe)) +- **input-component:** unset required attribute to prevent rendering it when + false + ([db9ac13](https://code.castopod.org/adaures/castopod/commit/db9ac13860bce58235a5da275910bea605a00626)) +- **install:** add password validation when creating super admin + ([5a2ca0c](https://code.castopod.org/adaures/castopod/commit/5a2ca0cc4ae85cc15960201c86f131cb822f714f)) +- **install:** redirect manually to install wizard on first visit + ([2ceaaca](https://code.castopod.org/adaures/castopod/commit/2ceaaca44f1b82fc64d961e2fb4f4aaeade7e736)) +- **install:** redirect to host_url install route on instanceConfig validation + error + ([99250b1](https://code.castopod.org/adaures/castopod/commit/99250b1868657c249a447399c7ebc69e00d43d1a)) +- **install:** redirect to input baseUrl after instance config + ([2426af7](https://code.castopod.org/adaures/castopod/commit/2426af7de8c9d426aaf534ff17b67f71c2e9f374)), + closes [#53](https://code.castopod.org/adaures/castopod/issues/53) +- **install:** set message block on forms to show error messages + ([3a0a20d](https://code.castopod.org/adaures/castopod/commit/3a0a20d59cdae7f166325efb750eaa6e9800ba6e)), + closes [#157](https://code.castopod.org/adaures/castopod/issues/157) +- **interact-as:** set actor_id instead of podcast id upon login event + ([5dfade7](https://code.castopod.org/adaures/castopod/commit/5dfade7cf37f339c56d2e577c679b88a1b1d9336)), + closes [#104](https://code.castopod.org/adaures/castopod/issues/104) +- **json-ld:** add missing properties to PodcastSeries object + ([e97266c](https://code.castopod.org/adaures/castopod/commit/e97266c5d4883a10f68b3685ecc0d1942f54d658)) +- keep subtitle line breaks when parsing srt file to json + ([cfb3da6](https://code.castopod.org/adaures/castopod/commit/cfb3da6592f2de23cb1a7ac420f19fc77fa338aa)) +- **layouts:** replace holy-grail layout with tailwind config + widen public + podcast layout + ([be5a287](https://code.castopod.org/adaures/castopod/commit/be5a28787fdb180b64d9bf570120eff7072ab9aa)) +- **map:** update episode markers query to discard unpublished episodes + ([b3caac4](https://code.castopod.org/adaures/castopod/commit/b3caac45b12a23e4289d00133d2ad7915d084c44)) +- **markdown-editor:** remove unnecessary buttons for podcast and episode + editors + add extensions + ([9c4f60e](https://code.castopod.org/adaures/castopod/commit/9c4f60e00bcbd4f784f12d2a6fed357ad402ee2e)) +- **md-editor:** build new markdown editor with lit + + github/markdown-toolbar-element + ([9ec1cb9](https://code.castopod.org/adaures/castopod/commit/9ec1cb93da6f41124c48b8cf14ee6942e865bede)), + closes [#93](https://code.castopod.org/adaures/castopod/issues/93) + [#94](https://code.castopod.org/adaures/castopod/issues/94) + [#120](https://code.castopod.org/adaures/castopod/issues/120) +- **migrations:** ignore invalid utf8 chars for media files metadata + update + transcript parser + ([45e8f99](https://code.castopod.org/adaures/castopod/commit/45e8f99e753cc02ec105e6f4d7fe026a205724f8)) +- minor corrections + ([13be386](https://code.castopod.org/adaures/castopod/commit/13be386842e94d9def1f7de4720931d8f6935171)) +- move analytics to helper + ([d311917](https://code.castopod.org/adaures/castopod/commit/d31191732e41aa106234b5ebe6e54ee02f0ce603)) +- move html escaping on credits page + ([fbffdbd](https://code.castopod.org/adaures/castopod/commit/fbffdbde78544c83138ee6234c62d43056f407b6)) +- **multiselect:** add missing class names in choices options for purge to work + properly + ([719538d](https://code.castopod.org/adaures/castopod/commit/719538d0ccb28af3c3c5e1a4b6468d4b772fe819)) +- **notifications:** add trigger after activities update + update insert trigger + ([e5d16e8](https://code.castopod.org/adaures/castopod/commit/e5d16e87119021fa5a43470d67ddfe5128e57f74)) +- **notifications:** notify actors after activities insert / update using model + callback methods + ([e08555a](https://code.castopod.org/adaures/castopod/commit/e08555a4e9a6c15eeba18273c63403f82eddae35)) +- **open-graph:** replace non existant episode description to podcast + description in podcast page + ([b02584e](https://code.castopod.org/adaures/castopod/commit/b02584ee609af1ad1b5680cc28208d113eb0410b)) +- overwrite common lang function to escape returned string + ([4c490c1](https://code.castopod.org/adaures/castopod/commit/4c490c15bb6642ad0b2aaddf08d8af25de99b4b0)), + closes [#196](https://code.castopod.org/adaures/castopod/issues/196) + [#198](https://code.castopod.org/adaures/castopod/issues/198) +- overwrite getActorById to return app's Actor entity + ([f2bc2f7](https://code.castopod.org/adaures/castopod/commit/f2bc2f7e01aa166faa627df6fe4d5ed4887c16e5)) +- **package.json:** update destination of postcss generation scripts + ([21413f8](https://code.castopod.org/adaures/castopod/commit/21413f8af3b8a0ac01d8c6f15bcd7a63e524e964)) +- **pages:** add locale to page cache + ([8f999ce](https://code.castopod.org/adaures/castopod/commit/8f999ce2f7ee1416c30cf58c84f67b3d11b3f142)) +- **partner:** set correct image URL + ([61554be](https://code.castopod.org/adaures/castopod/commit/61554be12a64d59ab99fab810b1b05632b408f3a)) +- pass timezone to relative time component to show the localized time in the UI + ([b9db936](https://code.castopod.org/adaures/castopod/commit/b9db936461d4cb914958bb3256bb910bbd7ba815)) +- **persons:** prevent overflow of persons list by adding horizontal scroll + ([9e8995d](https://code.castopod.org/adaures/castopod/commit/9e8995dc6e039032cc65f87895cf770f99e8b244)) +- **persons:** set person picture as optional for better ux + ([7fdea63](https://code.castopod.org/adaures/castopod/commit/7fdea63de7e572810082c84fff3013af580df58b)), + closes [#125](https://code.castopod.org/adaures/castopod/issues/125) +- **platforms:** display platform link only when visible is toggled on + ([6e503c8](https://code.castopod.org/adaures/castopod/commit/6e503c8d6182987e48892370623183f871bbd1c1)), + closes [#39](https://code.castopod.org/adaures/castopod/issues/39) +- **player-styling:** revert vite to 2.8 to reference the player css + ([e07d3af](https://code.castopod.org/adaures/castopod/commit/e07d3afea9af85b8361227e000fb64b502781668)) +- **podcast-activity:** check if transcript and chapters are set before + including them in audio + ([5855a25](https://code.castopod.org/adaures/castopod/commit/5855a250936f91641efef77650890a18d8e9917f)) +- **podcast-import:** move guid attribute declaration for Episode entity to + include slug data + ([5d02ae3](https://code.castopod.org/adaures/castopod/commit/5d02ae39908a9d743627135b372bf981134c4328)) +- **podcast:** use markdown description value for editor + set prose class to + about description + ([f304d97](https://code.castopod.org/adaures/castopod/commit/f304d97b14e0ef383509cb3bba50beb55bf701ba)), + closes [#156](https://code.castopod.org/adaures/castopod/issues/156) +- prefill description footer input when creating a new episode + ([9ea5ca3](https://code.castopod.org/adaures/castopod/commit/9ea5ca31697c70d176294f8aea37bd57d471fcf7)) +- **premium-podcasts:** display unlock button in embed when premium episode + ([ca109ba](https://code.castopod.org/adaures/castopod/commit/ca109ba3a8a08e661fd2484454b1983c3418f15d)) +- **premium-podcasts:** remove cache in unlock form + redirect to podcast if + podcast is not premium + ([242352c](https://code.castopod.org/adaures/castopod/commit/242352c4d9cd936de14e8e8a5d78ebf1287b1f95)) +- **premium-podcasts:** return different cached page when podcast is unlocked + ([b1303c5](https://code.castopod.org/adaures/castopod/commit/b1303c525517498b0edfb9885ff36e08c72628b5)) +- **pwa:** add scope to webmanifests to allow installing an app per podcast + ([74c683e](https://code.castopod.org/adaures/castopod/commit/74c683eb44398a84443ec17903c3e002bb5ea9b9)) +- **pwa:** set app display as standalone in the webmanifests + ([7aa37d2](https://code.castopod.org/adaures/castopod/commit/7aa37d24ac13a1ee160c01a56b43621d7efcfbbc)) +- re-order graph values + ([35f633b](https://code.castopod.org/adaures/castopod/commit/35f633b4c71c087d1ddc9bba9e9bbe18de09204f)) +- redirect to non cached views when authenticated in public views + ([482b47b](https://code.castopod.org/adaures/castopod/commit/482b47ba6bdab7f27fc5704a559567228e07cd14)) +- **release:** add missing version number to castopod-host package + ([8f3e9d9](https://code.castopod.org/adaures/castopod/commit/8f3e9d90c14545d3f84d4469b26a53db4554b4dc)) +- remove cache from remote follow form to display error messages + ([90e4443](https://code.castopod.org/adaures/castopod/commit/90e44437bdf37d8024ef609b2f7336dbdfc3b974)) +- remove defer from js script declaration as it is a module + ([18ae557](https://code.castopod.org/adaures/castopod/commit/18ae557e97f1cef775cd1e75fb1fedee7f1c0cc9)) +- remove fixed size from podcast sidebar + rearrange account info + space out + import radio inputs + ([776eec6](https://code.castopod.org/adaures/castopod/commit/776eec6f0d533d6c92ebec16f7a9dbfcde1f41f4)) +- remove heavy image cover data from audio file metadata + ([f74403b](https://code.castopod.org/adaures/castopod/commit/f74403bd7a5089b760603abe36264e7615be0e78)) +- remove required for other_categories field and add podcast_id to latest + podcasts query + ([5417be0](https://code.castopod.org/adaures/castopod/commit/5417be0049288489a19c7b575aa77bd1e2bc0243)) +- remove required property to persons picture + ([c546be3](https://code.castopod.org/adaures/castopod/commit/c546be385b243014243ae93356006cd126d2f00d)), + closes [#125](https://code.castopod.org/adaures/castopod/issues/125) +- remove value escaping for form inputs and textareas + ([bc6dea2](https://code.castopod.org/adaures/castopod/commit/bc6dea2f8ad1cf0aee0eaa93151332fbac7fb771)) +- rename field status to task_status to get scheduled activities + ([4ff82a5](https://code.castopod.org/adaures/castopod/commit/4ff82a5f0a38dbbc9e272fca7df70ea5a190e334)) +- rename issue_templates labels + ([9f00305](https://code.castopod.org/adaures/castopod/commit/9f00305844e5a168e89d727fe29892b4ad5e48d6)) +- rename MyAccount controller file + ([e109df3](https://code.castopod.org/adaures/castopod/commit/e109df3004a3a98d72de39532e062fff9917f50f)), + closes [#60](https://code.castopod.org/adaures/castopod/issues/60) +- rename podcast name to podcast handle to clarify field usage + ([9dd4c77](https://code.castopod.org/adaures/castopod/commit/9dd4c7741eb1b7cb5fc214ff674697f3aa986df0)), + closes [#126](https://code.castopod.org/adaures/castopod/issues/126) +- reorder fields as composite primary keys for analytics tables + ([9660aa9](https://code.castopod.org/adaures/castopod/commit/9660aa97c8ffd4fe61f3a388d52b9ac5dd8e1d63)) +- replace deletedField with published_at for episodes + ([14d7d07](https://code.castopod.org/adaures/castopod/commit/14d7d078225cdc8980759273a5dc4163d9f84b06)) +- replace getWebEnclosureUrl with getEnclosureWebUrl + ([8122cea](https://code.castopod.org/adaures/castopod/commit/8122ceaf8a70050f14b3078f28b024e7d7cdb9ac)) +- replace hardcoded style links with vite service + set default value for remote + transcript url + ([3f2e056](https://code.castopod.org/adaures/castopod/commit/3f2e05608e43d47bbb518a9acfaf56ec3eefafb4)), + closes [#149](https://code.castopod.org/adaures/castopod/issues/149) + [#150](https://code.castopod.org/adaures/castopod/issues/150) +- replace website key for webpages in breadcrumb translate file + ([50e32ff](https://code.castopod.org/adaures/castopod/commit/50e32ff75636c1d4c5d945a267e884cb26ad7191)) +- restore default podcast icon on public website + ([342778b](https://code.castopod.org/adaures/castopod/commit/342778bac3c684328d72633961df1a2ebdc1330e)) +- revert to beta.1's codeigniter4 version + ([e831411](https://code.castopod.org/adaures/castopod/commit/e83141127080ccde44987195db46ba97fd6cc2ca)) +- rewrite regenerate image function to use saveSizes method from Image entity + ([3889912](https://code.castopod.org/adaures/castopod/commit/38899124ec27e94a8c798bc2db528f9f785eec20)) +- **router:** check if Accept header is set before getting value + ([10a2ae0](https://code.castopod.org/adaures/castopod/commit/10a2ae02484672d6a0fbc6e7b943519c5ec16cb6)), + closes [#228](https://code.castopod.org/adaures/castopod/issues/228) +- **router:** trim URI slash to match same routes for URIs with and without + trailing slash + ([9e9375f](https://code.castopod.org/adaures/castopod/commit/9e9375f9a2cd6102f827b36ec521f4c86a557c00)) +- **rss-import:** add Castopod user-agent, handle redirects for downloaded + files, add Content namespace + ([214243b](https://code.castopod.org/adaures/castopod/commit/214243b3fec4937e45ef1ceaba1149004cdf3b44)) +- **rss:** cast number type values to string in rss_helper + ([7180ae9](https://code.castopod.org/adaures/castopod/commit/7180ae9ec700930b69c04ed91f8eceea16ad77ce)), + closes [#148](https://code.castopod.org/adaures/castopod/issues/148) +- **rss:** do not escape podcast and episode titles in the xml + ([0dd3b7e](https://code.castopod.org/adaures/castopod/commit/0dd3b7e0bf00d5a9eb80c93cba1efcada59ec3c1)), + closes [#138](https://code.castopod.org/adaures/castopod/issues/138) + [#71](https://code.castopod.org/adaures/castopod/issues/71) +- **rss:** remove escaping for publisher and owner name + ([6fc6347](https://code.castopod.org/adaures/castopod/commit/6fc6347846c126618cb7ff50164181650308d0c0)) +- **rss:** round episode durations and soundbites + ([c9fb987](https://code.castopod.org/adaures/castopod/commit/c9fb987fcfbe17069ec68fdbc823777079ce574b)), + closes [#214](https://code.castopod.org/adaures/castopod/issues/214) +- **rss:** set ❬itunes:author❭ tag to owner_name if publisher not specified + ([2271c14](https://code.castopod.org/adaures/castopod/commit/2271c1445b1ded12bc53b5d23b5e59d12b17c71a)), + closes [#96](https://code.castopod.org/adaures/castopod/issues/96) +- **rss:** use originalPath instead of originalMediaPath in Image library + ([b4012b7](https://code.castopod.org/adaures/castopod/commit/b4012b7d2ed6b34b69ad767570dd33f0dc7db920)) +- save transcript and chapters files to podcasts folder + ([63f49c7](https://code.castopod.org/adaures/castopod/commit/63f49c719f672b615c5a8893d3868dffcd332e47)) +- **search-episodes:** add fallback sql query using LIKE for search query with + less than 4 characters + ([e66bf44](https://code.castopod.org/adaures/castopod/commit/e66bf44341175bc5a10fbf7dfa00b351e76136c2)), + closes [#236](https://code.castopod.org/adaures/castopod/issues/236) +- **security:** add csrf filter + prevent xss attacks by escaping user input + ([cd2e1e1](https://code.castopod.org/adaures/castopod/commit/cd2e1e1dc37c53d32d00971c451c4800b8fd6107)) +- set cache expiration to next note publish to show note on publication date + ([0a66de3](https://code.castopod.org/adaures/castopod/commit/0a66de3e6c17d4ac94ee8e13bd00ceaf64b1303e)) +- set episode description footer to null when empty value + ([3a7d97d](https://code.castopod.org/adaures/castopod/commit/3a7d97d660046d80698611311ff3708110d2af82)) +- set episode duration translation to hardcoded english + ([c39efc9](https://code.castopod.org/adaures/castopod/commit/c39efc9489180662edcebd142d4476c0617ea97f)), + closes [#64](https://code.castopod.org/adaures/castopod/issues/64) +- set episode guid upon episode creation + ([ad8b153](https://code.castopod.org/adaures/castopod/commit/ad8b153f2a3b1a3b1751bf63785c4950e1516e6b)), + closes [#48](https://code.castopod.org/adaures/castopod/issues/48) +- set episode numbers during import + remove all custom form_helpers + minor ui + issues + ([99a3b8d](https://code.castopod.org/adaures/castopod/commit/99a3b8d33e00482da50dd62bdaa9215a351a56e4)) +- set interact_as_actor for user upon password reset + ([ad8f5f5](https://code.castopod.org/adaures/castopod/commit/ad8f5f5a0fac7b0b9cc10a0b86200f014aca7553)), + closes [#178](https://code.castopod.org/adaures/castopod/issues/178) +- set localized slug_field key as string in french language + ([17fb29b](https://code.castopod.org/adaures/castopod/commit/17fb29b20993b7deee4e252e0e3a4a2459ee0d98)) +- set location to null when getting empty string + ([71b1b5f](https://code.castopod.org/adaures/castopod/commit/71b1b5f775af475b1dc78328330e277f565e41b6)) +- set storage limit as disk_total_space instead of free space + ([7512e2e](https://code.castopod.org/adaures/castopod/commit/7512e2ed1ff5656cd63a4fc2524296dbb8b4164a)) +- **settings:** add .jpg extension to site-icon file input to display all jpeg + images + ([f611a16](https://code.castopod.org/adaures/castopod/commit/f611a16cd0c1a389e1c5a287eaec9d2a927a4bb6)) +- **socialinteract:** move social interact uri into uri attribute + update + social data upon import + ([12b2200](https://code.castopod.org/adaures/castopod/commit/12b22008a237185cb736fc29352fab22421dad16)) +- sort episodes by published_at with unpublished episodes at the begining + ([1686f84](https://code.castopod.org/adaures/castopod/commit/1686f840d16f2bd3d71d7f222a59b8e6a838fd6e)), + closes [#249](https://code.castopod.org/adaures/castopod/issues/249) +- sort episodic podcasts by season + ([d7b6794](https://code.castopod.org/adaures/castopod/commit/d7b6794f68f9a01fd606a407c6eb4c12d15dee74)) +- **themes:** update themes stylesheet route and remove css extension + ([e4e7e00](https://code.castopod.org/adaures/castopod/commit/e4e7e0005e931967dd6162588f1c5913dbf4603e)) +- **types:** update fake seeders types + fix bugs + ([76a4bf3](https://code.castopod.org/adaures/castopod/commit/76a4bf344160df679db29e236e7df7822970fb60)) +- **ui:** remove empty tooltip when hovering on sponsor button + ([40aa661](https://code.castopod.org/adaures/castopod/commit/40aa661289e1d1517fffcea5d257183bc9c458e4)) +- unpublish episode before deleting it + add validation step before deletion + ([f75bd76](https://code.castopod.org/adaures/castopod/commit/f75bd76458eeb01a2d37912695e33f77d03b7a69)), + closes [#112](https://code.castopod.org/adaures/castopod/issues/112) + [#55](https://code.castopod.org/adaures/castopod/issues/55) +- update .htaccess for shared hosting config + ([2379826](https://code.castopod.org/adaures/castopod/commit/2379826352e2f4b5060910bf9f29268610102f2e)) +- update broken contributor dropdown fields + ([e5b7515](https://code.castopod.org/adaures/castopod/commit/e5b75150234bd7f19e01def93425d3bda7379dd3)) +- update condition in AnalyticsTrait + ([fbc0967](https://code.castopod.org/adaures/castopod/commit/fbc0967caa81630d514ddb1b93b0834ebb4d913b)) +- update condition in home controller to redirect to install page + ([33f1b91](https://code.castopod.org/adaures/castopod/commit/33f1b91d55dd0652c979d50fc85879dbf88a4a42)) +- update conditions when checking for empty max_episodes and season_number + ([fbad0b5](https://code.castopod.org/adaures/castopod/commit/fbad0b59f68c65eba2fdcd5a8d3b312b622e9a45)) +- update form_textarea to prevent escaping value + ([78548b5](https://code.castopod.org/adaures/castopod/commit/78548b5cd75ea7d6688d1945ff5449ea4f6bec68)) +- update iso-369 language table seeder + ([0c90db4](https://code.castopod.org/adaures/castopod/commit/0c90db44c40de5af5b0b32b54489bda9424d9ef6)) +- update ivoox podcasting icon + ([f2b69a4](https://code.castopod.org/adaures/castopod/commit/f2b69a47339c887f57883ec612f3d200e512ac1c)) +- update MarkdownEditor component + restyle Button and other components + ([b05d177](https://code.castopod.org/adaures/castopod/commit/b05d177f1b7f44fef043ac5eb41f07133a2cf52d)) +- update purgecss content path for php helper files + ([eb70bb4](https://code.castopod.org/adaures/castopod/commit/eb70bb4f7078ff347aeb8f5dcc7896311d289466)), + closes [#59](https://code.castopod.org/adaures/castopod/issues/59) +- update translations for settings' tasks to include what they should be used + for + ([06b1a8b](https://code.castopod.org/adaures/castopod/commit/06b1a8b29b6ce5d81c5570d250bdac4e0c9ee5ca)) +- use slash instead of backslash to call layout + ([a80adb2](https://code.castopod.org/adaures/castopod/commit/a80adb22958fc0a38374cbce2d950a0042e699eb)) +- use UTC_TIMESTAMP() to get current utc date instead of NOW() in sql queries + ([4e22a0d](https://code.castopod.org/adaures/castopod/commit/4e22a0d5e4b60941d41071f059aac80cbaf38fbf)) +- **users:** remove required roles input when editing user + prevent owner's + roles from being edited + ([1c8af75](https://code.castopod.org/adaures/castopod/commit/1c8af7550ba27d8c8473ae96acd21ad7731fd863)), + closes [#239](https://code.castopod.org/adaures/castopod/issues/239) +- **ux:** allow for empty message upon episode publication and warn user on + submit + ([33d01b8](https://code.castopod.org/adaures/castopod/commit/33d01b8d4fd6ebf24e9f011aa705c456c846956c)), + closes [#129](https://code.castopod.org/adaures/castopod/issues/129) +- **ux:** have podcast dashboard card link to podcast dashboard if only one + podcast in instance + ([7dabee5](https://code.castopod.org/adaures/castopod/commit/7dabee58a187abe92358d962da506a836e29cda3)) +- **ux:** redirect user to install page on database error in home page + ([9017e30](https://code.castopod.org/adaures/castopod/commit/9017e30bf41bed8c2be65091bbc5fb1e63aef87a)) +- validate slug length when submitting episode form + clean permalink edit + prefix + ([b07ac09](https://code.castopod.org/adaures/castopod/commit/b07ac093b2cae646f9a897bc9dfeeaef6eda6561)) +- **video-clips:** check if created video exists before recreating it and + failing + ([dff1208](https://code.castopod.org/adaures/castopod/commit/dff12087251b2b89e195604202094b5ddd9a0936)) +- **video-clips:** clear video clip cache after process has finished + ([3ae6232](https://code.castopod.org/adaures/castopod/commit/3ae62325856f6ff331a5d9ed901b9fa097ca7055)) +- **video-clips:** create unique temporary files for resources to be deleted + after generation + ([7f7c878](https://code.castopod.org/adaures/castopod/commit/7f7c878cb6ecf7b4a967b2af87da82bc6593081e)) +- **video-clips:** set audio codec to aac, fixing audio issue on twitter + ([3c22c68](https://code.castopod.org/adaures/castopod/commit/3c22c68ee81f77bd7fcf7e2739ee6af016407843)) +- **video-clips:** set longer podcast and episode lengths for squared format + ([c030113](https://code.castopod.org/adaures/castopod/commit/c0301134c2048dc29eb2b995e4d5c22c49444100)) +- **video-clips:** tweak portrait parameters to have subtitles display without + overflowing + ([2385b1a](https://code.castopod.org/adaures/castopod/commit/2385b1a2926d1344569836e18cb30adb4c604664)) +- **video-clips:** update condition to check if ffmpeg is installed + ([b57f0b6](https://code.castopod.org/adaures/castopod/commit/b57f0b6eb65dccf22cb4d55f93d18ca36857d7fc)), + closes [#163](https://code.castopod.org/adaures/castopod/issues/163) +- **xml-editor:** escape xml editor's content + restyle form sections to prevent + overflowing + ([588590b](https://code.castopod.org/adaures/castopod/commit/588590bd2c0346e2465ff8f1930580d76a3bf068)) +- **xml-editor:** prettify xml even without root node + ([ca55c24](https://code.castopod.org/adaures/castopod/commit/ca55c248d0562a8529071c1f10be12f40ef50dda)) + +### Features + +- **activitypub:** add Podcast actor and PodcastEpisode object with comments + ([9e1e5d2](https://code.castopod.org/adaures/castopod/commit/9e1e5d2e862d6a3345d11ca7f96b955c76bfa013)) +- add about page in admin with instance info + database update button + ([d0836f3](https://code.castopod.org/adaures/castopod/commit/d0836f3ee360a836f815c59ea755f288501dc517)) +- add alternate rss feed link tag to podcast page head + ([a973c09](https://code.castopod.org/adaures/castopod/commit/a973c097d54a3d0186c4079b9d4d3e81aae38505)), + closes [#35](https://code.castopod.org/adaures/castopod/issues/35) +- add analytics and unknown useragents + ([ec92e65](https://code.castopod.org/adaures/castopod/commit/ec92e65aa42e09b1df04600b52a0c679dfc494bb)) +- add audio-clipper toolbar + add video-clip-previewer + ([0255753](https://code.castopod.org/adaures/castopod/commit/02557539e6eb48fc23ee2ee3b0c75aee3310965b)) +- add audio-clipper webcomponent (wip) + ([21d4251](https://code.castopod.org/adaures/castopod/commit/21d4251b9bcd5acb0f8a1761bc4edc34a3dbc228)) +- add autofocus to input field "Email or username" on login page + ([19caed4](https://code.castopod.org/adaures/castopod/commit/19caed4bce0daab9ccf6ab9645f44b60eb87de88)) +- add basic stats on podcast about page + ([1670558](https://code.castopod.org/adaures/castopod/commit/1670558473dba47219d470ff21d6224db6ab42ba)) +- add breadcrumb in admin area + ([7fb1de2](https://code.castopod.org/adaures/castopod/commit/7fb1de2cf3c97c4cd7afe3bd71bbe66041786ecd)), + closes [#17](https://code.castopod.org/adaures/castopod/issues/17) +- add cache to ActivityPub sql queries + cache activity and note pages + ([2d297f4](https://code.castopod.org/adaures/castopod/commit/2d297f45b3d7ef6e8711875a0b9b908e878115fa)) +- add CDN url + ([972bcbf](https://code.castopod.org/adaures/castopod/commit/972bcbf65ee119b8641ca3c4e5c0e8cf9ca8dd4f)), + closes [#37](https://code.castopod.org/adaures/castopod/issues/37) +- add codemirror to display xml editor for custom rss field + ([f15f262](https://code.castopod.org/adaures/castopod/commit/f15f26240cd5311fa9d07779f364b6639a501dec)) +- add cumulative listening time charts + ([588b4d2](https://code.castopod.org/adaures/castopod/commit/588b4d28da00bc12d02126e23181690f54d81716)) +- add default icons to Alert component + ([0d98001](https://code.castopod.org/adaures/castopod/commit/0d9800123b135e4fa1a2acd14a5e039c12174333)) +- add DropdownMenu component + remove global audio player in admin + ([abb7fba](https://code.castopod.org/adaures/castopod/commit/abb7fbac276d77b7d31a0aeba75d464f3ba3ad46)) +- add episode_numbering() component helper to display episode and season numbers + ([3f4a6bd](https://code.castopod.org/adaures/castopod/commit/3f4a6bd0b9f870f16107a41b102b6bf734868198)) +- add french translation + ([196920d](https://code.castopod.org/adaures/castopod/commit/196920d62f1810b4c35f800d17d7f93627319091)) +- add heading component + update ecs rules to fix views + ([23bdc6f](https://code.castopod.org/adaures/castopod/commit/23bdc6f8e36b7e8dfbe32755a54dea59ad913432)) +- add housekeeping task to run after migrations + ([89dee41](https://code.castopod.org/adaures/castopod/commit/89dee41d583e57251ea9315402a757f03571d7ad)) +- add install wizard form to bootstrap database and create the first superadmin + user + ([cba871c](https://code.castopod.org/adaures/castopod/commit/cba871c5df9f7120c44d9952456ebbd0d220669e)), + closes [#2](https://code.castopod.org/adaures/castopod/issues/2) +- add instructions on production error page to ease Castopod debugging process + ([9eab54e](https://code.castopod.org/adaures/castopod/commit/9eab54e0853ccb8300d9f9b743cd84aefbf06549)), + closes [#224](https://code.castopod.org/adaures/castopod/issues/224) +- add ISO 3166 country codes + ([97cd94b](https://code.castopod.org/adaures/castopod/commit/97cd94b47494b66faf43fbbe0748872da80020a4)) +- add js audio player on podcast, admin and embeddable player pages + fix admon + episodes ux + ([0e14eb4](https://code.castopod.org/adaures/castopod/commit/0e14eb4d3f526b0fd256a6144f3fbfc3fe52a357)), + closes [#131](https://code.castopod.org/adaures/castopod/issues/131) +- add label to sponsor button on podcast page + ([c29c018](https://code.castopod.org/adaures/castopod/commit/c29c018c7a543fc9398b5d7d11f086123e2b33f2)), + closes [#162](https://code.castopod.org/adaures/castopod/issues/162) +- add legalNoticeURL to app config for setting an external url to legal notice + ([711843a](https://code.castopod.org/adaures/castopod/commit/711843a0c81e1e2ec7a015431786df4ef32d5092)) +- add lock podcast according to the Podcastindex podcast-namespace to prevent + unauthozized import + ([72b3012](https://code.castopod.org/adaures/castopod/commit/72b301272e0b70ded3e2b237391909e3f152ad0b)) +- add map analytics, add episodes analytics, clean analytics page layout, + translate countries + ([07eae83](https://code.castopod.org/adaures/castopod/commit/07eae83a00d860e149359fae67d549488403d88b)) +- add media entity and link documents, images and audio files to it + ([6ecf286](https://code.castopod.org/adaures/castopod/commit/6ecf2866cfcde31a0840f15c3340808ce14b44cf)) +- add notifications inbox for actors + ([999999e](https://code.castopod.org/adaures/castopod/commit/999999e3efab7b1aad7568e4fd114dc7bac04f38)), + closes [#215](https://code.castopod.org/adaures/castopod/issues/215) +- add Noto Sans Mono font to use for durations + button to access new video clip + form in list + ([7609bb6](https://code.castopod.org/adaures/castopod/commit/7609bb60330539aa91bfdafbb35c2d585624218a)) +- add npm for js dependencies + move src/ files to root folder + ([cbb83a6](https://code.castopod.org/adaures/castopod/commit/cbb83a6f308ac9357e9fb0cca5edae9d3fee5b48)) +- add Open Graph and Twitter meta tags + ([af970b8](https://code.castopod.org/adaures/castopod/commit/af970b8bac949e4c63047e04aca1b7403a4e8deb)), + closes [#41](https://code.castopod.org/adaures/castopod/issues/41) +- add pages table to store custom instance pages (eg. legal-notice, cookie + policy, etc.) + ([9c224a8](https://code.castopod.org/adaures/castopod/commit/9c224a8ac6dd95f3c6c087a300fc8bac48e8090f)), + closes [#24](https://code.castopod.org/adaures/castopod/issues/24) +- add permanent delete feature for podcasts 🎉 + ([dbb4030](https://code.castopod.org/adaures/castopod/commit/dbb4030da49f9ea1f61759fb7c66d71fc29ea4a1)), + closes [#89](https://code.castopod.org/adaures/castopod/issues/89) +- add platform models + ([a333d29](https://code.castopod.org/adaures/castopod/commit/a333d291966229a909c0851fd8b890ed97c48ceb)) +- add platforms form in podcast settings + ([043f49c](https://code.castopod.org/adaures/castopod/commit/043f49c784bc007ca0fa756ca4ed2d3b08843ad9)) +- add platforms tables + ([ce59344](https://code.castopod.org/adaures/castopod/commit/ce5934419a516c9926dd3fd0ace3c11a95b60722)) +- add podcast banner field for each podcast + refactor images configuration + ([4a8147b](https://code.castopod.org/adaures/castopod/commit/4a8147bfbbd98d9badfc57a0f2a18bdd5812e802)) +- add premium podcasts to manage subscriptions for premium episodes + ([3234500](https://code.castopod.org/adaures/castopod/commit/3234500e2d967438ad140f65da801a543f43775d)), + closes [#193](https://code.castopod.org/adaures/castopod/issues/193) +- add publish feature for podcasts and set draft by default + ([3d363f2](https://code.castopod.org/adaures/castopod/commit/3d363f2efe99836ac05c305a2fa683e342f06561)), + closes [#128](https://code.castopod.org/adaures/castopod/issues/128) + [#220](https://code.castopod.org/adaures/castopod/issues/220) +- add remote_url alternative for transcript and chapters files + ([3143c9a](https://code.castopod.org/adaures/castopod/commit/3143c9ad36e4cf1364205cf2be39c0c96f80fdd2)) +- add replied to post or comment to reply element + ([d0f9c60](https://code.castopod.org/adaures/castopod/commit/d0f9c6018f1af527099f3e26b5d824710fa11caf)) +- add schema.org json-ld objects to podcasts, episodes, posts and comments pages + ([902f959](https://code.castopod.org/adaures/castopod/commit/902f959b30a10839684f093eb86edebc5d826a0b)) +- add task to housekeeping setting for resetting all instance counts + ([9303e51](https://code.castopod.org/adaures/castopod/commit/9303e51bc50d730a8026f58984e83b840360ee88)) +- add unique listeners analytics + ([3a49258](https://code.castopod.org/adaures/castopod/commit/3a4925816f3268230640525ad7af507aab8eecb9)) +- add update rss feed feature for podcasts to import their latest episodes + ([5eb9dc1](https://code.castopod.org/adaures/castopod/commit/5eb9dc168eb9af04767829b76242c9120f55d46d)), + closes [#183](https://code.castopod.org/adaures/castopod/issues/183) +- add user permissions and basic groups to handle authorizations + ([d58e518](https://code.castopod.org/adaures/castopod/commit/d58e51874a4722921b75b0049117015c2380406e)), + closes [#3](https://code.castopod.org/adaures/castopod/issues/3) + [#18](https://code.castopod.org/adaures/castopod/issues/18) +- add WebSub module for pushing feed updates to open hubs + ([10d3f73](https://code.castopod.org/adaures/castopod/commit/10d3f73786ba141e27a822b2585c4a244ee92c14)) +- **admin:** add instance wide dashboard with storage and bandwidth usage + ([b1a6c02](https://code.castopod.org/adaures/castopod/commit/b1a6c02e56fdc01a7ff69fa7e7dd8ea71380b7ba)), + closes [#216](https://code.castopod.org/adaures/castopod/issues/216) +- **admin:** add search form in podcast episodes list + ([6be5d12](https://code.castopod.org/adaures/castopod/commit/6be5d12877342a7c56e25ea8dd15a975c6ce45ac)), + closes [#26](https://code.castopod.org/adaures/castopod/issues/26) +- **admin:** make header stick on scroll and show title + action buttons using + css only + ([d60498c](https://code.castopod.org/adaures/castopod/commit/d60498c1beb970a14eeb3bbe02d1b1d8116624b0)) +- **admin:** update admin layout for better ux + update brand pine colors + ([d86142e](https://code.castopod.org/adaures/castopod/commit/d86142ebe7cd7582835f180b79fbeaaaba703528)) +- allow cross origin requests on episode comments + ([e12f95a](https://code.castopod.org/adaures/castopod/commit/e12f95aca13c6d54489a9cfd99d4cd2490fe83ab)) +- **analytics-gdpr:** update cached personal data to expire at midnight + ([0188b67](https://code.castopod.org/adaures/castopod/commit/0188b67354a756f0c926edd7b46623ab5b20c12b)) +- **analytics:** add 'other' group to pie charts in order to display more + accurate data + ([73acef9](https://code.castopod.org/adaures/castopod/commit/73acef933ff3485987afc5157de022910876fc12)) +- **analytics:** add charts and data export + ([78625c4](https://code.castopod.org/adaures/castopod/commit/78625c471b4f03a09bd42f72b82217e1f2d01cef)) +- **analytics:** add current date and secret salt to analytics hash for improved + privacy + ([6f2e7c0](https://code.castopod.org/adaures/castopod/commit/6f2e7c009c24830d4f08633bfbde3b75f40bf215)) +- **analytics:** add service name from rss user-agent + ([7202b98](https://code.castopod.org/adaures/castopod/commit/7202b9867bd59aafa8c338a4230fb5e5c55b24c6)) +- **analytics:** add weekday and hour bar charts + ([8ab3132](https://code.castopod.org/adaures/castopod/commit/8ab313296bb4a254ab05e90b17d896039839b784)) +- **api:** add rest api with podcasts read endpoints + ([e64001d](https://code.castopod.org/adaures/castopod/commit/e64001d00604bcf587ec5e9a631282f212df450d)), + closes [#210](https://code.castopod.org/adaures/castopod/issues/210) +- apply colour theme to embed player + ([9548337](https://code.castopod.org/adaures/castopod/commit/9548337a7c49879e8b58c2dfece46e3cfc9517eb)), + closes [#201](https://code.castopod.org/adaures/castopod/issues/201) +- **auth:** add auth.enable2FA config to enable two-factor authentication + ([7213ed2](https://code.castopod.org/adaures/castopod/commit/7213ed290c977ce8723f6d92addadc03913576ee)) +- build hashed static files to renew browser cache + ([37c54d2](https://code.castopod.org/adaures/castopod/commit/37c54d247749bdf8f528babd4a78f24d48051063)), + closes [#107](https://code.castopod.org/adaures/castopod/issues/107) +- **cache:** add podcast and episode pages to cache + clear them after insert or + update + ([da0f047](https://code.castopod.org/adaures/castopod/commit/da0f0472819007e02e5da37399f2377772c618b9)) +- **categories:** create model, entity, migrations and seeds + ([f73b042](https://code.castopod.org/adaures/castopod/commit/f73b042cc091be82abdbbca8992080875d526972)) +- **clips:** setup clip entities and model + save video clip to have it + generated in the background + ([2f6fdf9](https://code.castopod.org/adaures/castopod/commit/2f6fdf9091d52ca49709fc82621ba1c6dd0e817d)) +- **comments:** add comments to episodes + update naming of status to post + ([bb4752c](https://code.castopod.org/adaures/castopod/commit/bb4752c35e086664f5fd75fdc0d56546a1e356f6)) +- **comments:** add like / undo like to comment + add comment page + ([0c187ef](https://code.castopod.org/adaures/castopod/commit/0c187ef7a9278a60bcc6e5ee4d69d948b51e5c54)) +- **components:** add custom view renderer with ComponentRenderer adapted from + bonfire2 + ([a95de8b](https://code.castopod.org/adaures/castopod/commit/a95de8bab010f6b01c598da72191abe97e473687)) +- create optimized & resized images upon upload + ([02e4441](https://code.castopod.org/adaures/castopod/commit/02e4441f98f27e9534e5b9b63279153d14632ccd)), + closes [#6](https://code.castopod.org/adaures/castopod/issues/6) +- **custom-rss:** add custom xml tag injection in rss feed for ❬channel❭ and + ❬item❭ + ([6ecdaad](https://code.castopod.org/adaures/castopod/commit/6ecdaad911d06b7f7a2b7d24710968c7eb9118f6)) +- **datetime-picker:** set material_green theme to flatpickr + ([3ce6541](https://code.castopod.org/adaures/castopod/commit/3ce6541003260677e722a916ad6bc83ef47c4371)) +- **devcontainer:** add devcontainer settings for dev environment + ([69e7266](https://code.castopod.org/adaures/castopod/commit/69e72667365247b63430dee88194e8f0d7c28edc)) +- display castopod version in admin footer + ([9f2574e](https://code.castopod.org/adaures/castopod/commit/9f2574e6fbb61dac4e1a4252dff30017685da5f0)), + closes [#68](https://code.castopod.org/adaures/castopod/issues/68) +- display legal disclaimer and warning on podcast import page + ([2f07992](https://code.castopod.org/adaures/castopod/commit/2f07992e5508b34b91f194eebfac80c51e80e90a)), + closes [#34](https://code.castopod.org/adaures/castopod/issues/34) +- edit + delete podcast and episode + ([ac5f0c7](https://code.castopod.org/adaures/castopod/commit/ac5f0c732806e955c01e05b7867801bc938c6bd5)) +- **embeddable-player:** add embeddable player widget + ([141788f](https://code.castopod.org/adaures/castopod/commit/141788fa089f9dedc8956c64ca515a4a4625f904)) +- enhance admin ui with responsive design and ux improvements + ([2d44b45](https://code.castopod.org/adaures/castopod/commit/2d44b457a02205d2e7da258d7029b8bc5da39533)), + closes [#31](https://code.castopod.org/adaures/castopod/issues/31) + [#9](https://code.castopod.org/adaures/castopod/issues/9) +- enhance ui using javascript in admin area + ([c0e66d5](https://code.castopod.org/adaures/castopod/commit/c0e66d5f7012026e145d106f4d6bd3ba792a1b77)) +- **episode-unpublish:** remove episode comments upon unpublish + ([78acd7f](https://code.castopod.org/adaures/castopod/commit/78acd7f5c057c82507d801c424040296dbaba586)) +- **episode:** add form to allow editing episode's publication date to a past + date + ([d783d16](https://code.castopod.org/adaures/castopod/commit/d783d16eb73d3f896a3dea39a766b4e963e53abf)), + closes [#97](https://code.castopod.org/adaures/castopod/issues/97) +- **episodes:** add create form and view pages for episode + ([f3b2c8b](https://code.castopod.org/adaures/castopod/commit/f3b2c8b84f3d93bef734e34dbe8ed729535e45e9)), + closes [#1](https://code.castopod.org/adaures/castopod/issues/1) +- **episodes:** add migrations, model and entity for episodes table + ([0444821](https://code.castopod.org/adaures/castopod/commit/044482174ede555ce19a2d8c6f48771cc8e7d27b)) +- **episodes:** replace all audio file URL parameters with base64 encoded data + ([e1f65cd](https://code.castopod.org/adaures/castopod/commit/e1f65cd3b53353a30d4ab6eb5312393cf04a1676)) +- **episodes:** replace soft delete with permanent delete + ([eb9ff52](https://code.castopod.org/adaures/castopod/commit/eb9ff522c25af8ceb2ed08614b581757ee791d42)) +- **episodes:** schedule episode with future publication_date by using cache + expiration time + ([4f1e773](https://code.castopod.org/adaures/castopod/commit/4f1e773c0f9e4c2597f6c1b0a4773dfb34b2f203)), + closes [#47](https://code.castopod.org/adaures/castopod/issues/47) +- **fediverse:** implement activitypub protocols + update user interface + ([2f525c0](https://code.castopod.org/adaures/castopod/commit/2f525c0f6e44d320bff16e22c223481923ba683e)), + closes [#69](https://code.castopod.org/adaures/castopod/issues/69) + [#65](https://code.castopod.org/adaures/castopod/issues/65) + [#85](https://code.castopod.org/adaures/castopod/issues/85) + [#51](https://code.castopod.org/adaures/castopod/issues/51) + [#91](https://code.castopod.org/adaures/castopod/issues/91) + [#92](https://code.castopod.org/adaures/castopod/issues/92) + [#88](https://code.castopod.org/adaures/castopod/issues/88) +- **fonts:** replace Montserrat with Inter for better readablity + ([bfa11d0](https://code.castopod.org/adaures/castopod/commit/bfa11d007d04b8ac714c8cf3b8050a6aaf177a26)) +- **GDPR:** add GDPR.yml file to public/.well-known/ + ([86bccc3](https://code.castopod.org/adaures/castopod/commit/86bccc3d5cc9562b89196f1766ac91cdc8ad786d)) +- **gdpr:** add purpose for granting access to premium content + ([47d6d81](https://code.castopod.org/adaures/castopod/commit/47d6d81b798ec3ed467e0f4339c98c8a6b80cecd)) +- **home:** sort podcasts by recent activity + add dropdown menu to choose + between sorting options + ([7b89da6](https://code.castopod.org/adaures/castopod/commit/7b89da6106c150708782d39ed2742fe416c41e89)), + closes [#164](https://code.castopod.org/adaures/castopod/issues/164) +- **housekeeping:** add clear_cache option to flush redis or files cache + ([99bfac0](https://code.castopod.org/adaures/castopod/commit/99bfac0b428a4bc6fe8bfd10a355dfd93f42ba5c)) +- **i18n:** add 7 new languages + update german translations + ([d021abb](https://code.castopod.org/adaures/castopod/commit/d021abb52f5525d93810e25df2b453c918d7bc8b)) +- **i18n:** add german language as supported locale + create Language files from + english source + ([c220b31](https://code.castopod.org/adaures/castopod/commit/c220b310ed59cad188af044b1fed0c39efc7da5b)) +- **i18n:** add Norwegian Nynorsk to supported locales + ([ced61fc](https://code.castopod.org/adaures/castopod/commit/ced61fc2364f954c1f6e0208b572faf5741498a8)) +- **i18n:** add Polish translation + ([2d83b44](https://code.castopod.org/adaures/castopod/commit/2d83b44add9e4e00766a1f326377ed892f48ad73)) +- **i18n:** add Spanish to supported locales + ([e340b54](https://code.castopod.org/adaures/castopod/commit/e340b54a84d7dcdf9ba910fe7ff39c453fac0968)) +- **i18n:** add support for German and Brazilian Portuguese languages + ([c9b9fe4](https://code.castopod.org/adaures/castopod/commit/c9b9fe4ee893de9a1df7f8269c39d08a90d205d6)) +- **i18n:** add support for Simplified Chinese (zh-Hans) and Catalan (ca) + locales + ([48d1443](https://code.castopod.org/adaures/castopod/commit/48d14434727c3310a391160c7af02c56b7e20425)) +- **icons:** add default icons for podcasting, social and funding platforms + + remove complex icons + ([5bcdfeb](https://code.castopod.org/adaures/castopod/commit/5bcdfebe6489b5d6b90f3c828b014ec4e9a7e7e1)), + closes [#166](https://code.castopod.org/adaures/castopod/issues/166) + [#167](https://code.castopod.org/adaures/castopod/issues/167) + [#170](https://code.castopod.org/adaures/castopod/issues/170) +- **icons:** add podnews icon to podcasting platforms + ([5f42355](https://code.castopod.org/adaures/castopod/commit/5f423557c2b78fd7c38c5e0caab6c6c80d21e36e)), + closes [#190](https://code.castopod.org/adaures/castopod/issues/190) +- import podcast from an rss feed url + ([9a5d5a1](https://code.castopod.org/adaures/castopod/commit/9a5d5a15b4945eb319da9e999c4ca60a0a4f6d2d)), + closes [#21](https://code.castopod.org/adaures/castopod/issues/21) +- integrate stylized form components and update podcast edit page + ([6536729](https://code.castopod.org/adaures/castopod/commit/653672954606a23796e8a7bda3c34fd6b92f84e0)) +- make displayed publication time as relative time using @github/time-elements + ([230e139](https://code.castopod.org/adaures/castopod/commit/230e139e43324b9ebef06ca8f6e13b3d9a7bdc70)) +- make episode description more visible on episode pages + ([90533be](https://code.castopod.org/adaures/castopod/commit/90533be0298249e5527870c01329fce5f94ec2dc)), + closes [#171](https://code.castopod.org/adaures/castopod/issues/171) +- **map:** display geolocated episodes on a map page + ([4357cc2](https://code.castopod.org/adaures/castopod/commit/4357cc25ccc585ce398035c1c25d566b6a9df775)) +- **media:** clean media api + create an entity per media type + ([fafaa7e](https://code.castopod.org/adaures/castopod/commit/fafaa7e689b17f09a2b056081fa1f4fc53bf716b)) +- **media:** save audio, images, transcripts and chapters to media for episode + and persons + ([58e2a00](https://code.castopod.org/adaures/castopod/commit/58e2a00a87fa7d5b188e13cc521d94f0cfddba50)) +- **meta-tags:** add activitypub alternate links to podcast, episode, comment + and post pages + ([bd61752](https://code.castopod.org/adaures/castopod/commit/bd61752be2f574323b05d1d0aee0df55adf9a74e)) +- minor corrections to some tables + ([3bf9420](https://code.castopod.org/adaures/castopod/commit/3bf9420b5956a501b3b24405d243a71a928d6086)) +- **monetization:** add Web Monetization support + ([96a6026](https://code.castopod.org/adaures/castopod/commit/96a6026f1db452085360f5fe248de82a2ec06468)) +- **nodeinfo2:** add .well-known route for nodeinfo2 containing metadata about + the castopod instance + ([88fddc8](https://code.castopod.org/adaures/castopod/commit/88fddc81d730978f2a4d8a671936b54041e3fe45)) +- **partner:** add link and image in episode description + ([ad07bb9](https://code.castopod.org/adaures/castopod/commit/ad07bb9330dc9493813368e969e1f3a3def44614)) +- **person:** add podcastindex.org namespace person tag + ([8acd011](https://code.castopod.org/adaures/castopod/commit/8acd011f13e99492ef4b44b327685bb006fe5f8f)) +- **platforms:** add AntennaPod + ([53e9cfd](https://code.castopod.org/adaures/castopod/commit/53e9cfd61c794b1539e9d4691d3c4e73c4b7aaa7)) +- **platforms:** add Fediverse and some funding platforms, add link on logo + ([afc3d50](https://code.castopod.org/adaures/castopod/commit/afc3d50289bb4173e0697d109ffe72f6814b93d1)) +- **platforms:** add helloasso + ([16cb993](https://code.castopod.org/adaures/castopod/commit/16cb993ee6e28987a840fc27a9c2c73794c67697)) +- **platforms:** add missing newpodcastapps.com's platforms + ([92dd370](https://code.castopod.org/adaures/castopod/commit/92dd370e2f9a464edd26cddcde96d0e16f91548d)) +- **platforms:** add pod.link + ([3d7a232](https://code.castopod.org/adaures/castopod/commit/3d7a2320ddd116e4a311605421126aff57243219)) +- **platforms:** add Podcast Index + ([ad52b1c](https://code.castopod.org/adaures/castopod/commit/ad52b1cc2b7d0bc844970214d205961a7196b4a9)) +- **platforms:** add podfriend + ([9fdc8d3](https://code.castopod.org/adaures/castopod/commit/9fdc8d32930234c7ffd2be6892be57febcef1086)) +- **podcast-form:** add new_feed_url field to set an url when changing domain or + host + ([e7eec48](https://code.castopod.org/adaures/castopod/commit/e7eec48e7bc06a9aa907db01ed3e5b536e7dd8be)) +- **podcast-form:** update routes and redirect to podcast page + ([12ce905](https://code.castopod.org/adaures/castopod/commit/12ce905799002dc9c07e6de092342d30ba9fd7d8)) +- **podcast:** create a podcast using form + ([1202ba3](https://code.castopod.org/adaures/castopod/commit/1202ba3545f521097c60a6a2af95e70527cd1d34)) +- **podcasting 2.0:** update podcast:social tag to adhere to latest spec + ([a597cf4](https://code.castopod.org/adaures/castopod/commit/a597cf4ecfa6807a3413177d99c816056a7e7c45)) +- prefill season and episode numbers + set episode number as mandatory for + serial podcasts + ([07d740b](https://code.castopod.org/adaures/castopod/commit/07d740b79f9283e389e723954f680f909ce5de4a)), + closes [#134](https://code.castopod.org/adaures/castopod/issues/134) + [#136](https://code.castopod.org/adaures/castopod/issues/136) +- **public-ui:** adapt public podcast and episode pages to wireframes + ([40a0535](https://code.castopod.org/adaures/castopod/commit/40a0535fc1bc12a24994b651f5e00b35995cbdda)), + closes [#30](https://code.castopod.org/adaures/castopod/issues/30) + [#13](https://code.castopod.org/adaures/castopod/issues/13) +- **pwa:** add service-worker + webmanifest for each podcasts to have them + install on devices + ([fee2c1c](https://code.castopod.org/adaures/castopod/commit/fee2c1c0d0d03c4ff0a6a207b0a5e0c22bb7b13a)) +- redesign public podcast and episode pages + remove any information clutter for + better ux + ([9321400](https://code.castopod.org/adaures/castopod/commit/932140077c671f0486a2cd08ceb6126c7ecde87f)) +- replace form helper functions with components in admin template + ([e64548b](https://code.castopod.org/adaures/castopod/commit/e64548b982ba47ff35f2272e2e30dd85eeba950b)) +- replace slug field with interactive permalink component + ([578022b](https://code.castopod.org/adaures/castopod/commit/578022b8c5163ffaf8db5870ed5ec9d5d9536477)) +- restyle episode and person cards + add focus style to interactive elements for + a11y + ([a505a1d](https://code.castopod.org/adaures/castopod/commit/a505a1de56e8e3056379bd60d0595f432e294728)) +- **rss:** add ˂podcast:guid˃ tag for channel + ([1fab10e](https://code.castopod.org/adaures/castopod/commit/1fab10eb0d63bb7c3edf34ffe691e2aec2c2e43c)) +- **rss:** add podcast-namespace tags for platforms + previousUrl tag + ([dbba8dc](https://code.castopod.org/adaures/castopod/commit/dbba8dc58133967c778514268cbfed8098ed1dbc)), + closes [#73](https://code.castopod.org/adaures/castopod/issues/73) + [#75](https://code.castopod.org/adaures/castopod/issues/75) + [#76](https://code.castopod.org/adaures/castopod/issues/76) + [#80](https://code.castopod.org/adaures/castopod/issues/80) +- **rss:** add podcast:comments tag to link to episode comments + ([32e8c7c](https://code.castopod.org/adaures/castopod/commit/32e8c7c16a61ffe08e2f3bfbdeda556811a0358c)) +- **rss:** add podcast:location tag + ([c0a2282](https://code.castopod.org/adaures/castopod/commit/c0a22829bd87d48535a86e60c6cd7280e44683a2)) +- **rss:** add rss feed route without the `.xml` extension + ([94c0b7c](https://code.castopod.org/adaures/castopod/commit/94c0b7c15920dae9ade5cdc79c7996dbfe82ba05)), + closes [#247](https://code.castopod.org/adaures/castopod/issues/247) +- **rss:** add soundbites according to the podcastindex specs + ([6b34617](https://code.castopod.org/adaures/castopod/commit/6b34617d07c70522cb941e96d91d9987493413eb)), + closes [#83](https://code.castopod.org/adaures/castopod/issues/83) +- **rss:** add transcript and chapters support + ([e769d83](https://code.castopod.org/adaures/castopod/commit/e769d83a932c169e52a630a17cd4dd8ac5cebaf6)), + closes [#72](https://code.castopod.org/adaures/castopod/issues/72) + [#82](https://code.castopod.org/adaures/castopod/issues/82) +- **rss:** generate rss feed from podcast entity + ([c815ecd](https://code.castopod.org/adaures/castopod/commit/c815ecd6640931fee0895f80908a3ddfac482666)) +- **rss:** update monetization tag so that it meets PodcastIndex requirements + ([4c7ecbe](https://code.castopod.org/adaures/castopod/commit/4c7ecbee83950e5f9f2482cedaab18a1ac9bfc9e)) +- **select:** enhance select input with choices.js + ([910d457](https://code.castopod.org/adaures/castopod/commit/910d457cf843e0fc334b3505a4727d51633395ac)) +- set app parameter forceGlobalSecureRequests = true forcing requests to go + through https + ([d9dff1b](https://code.castopod.org/adaures/castopod/commit/d9dff1b8bf89c8b526ad6cb89f98a1f160d49117)) +- set podcast / episode description in the pages description meta tag + ([1c4a504](https://code.castopod.org/adaures/castopod/commit/1c4a50442bea2d3449efce9c5ff1c80743152f55)), + closes [#44](https://code.castopod.org/adaures/castopod/issues/44) +- **settings:** add general config for instance (site name, description and + icon) + ([5c56f3e](https://code.castopod.org/adaures/castopod/commit/5c56f3e6f00a61af2ccf50811c155c325f2b10fa)) +- **settings:** add theme settings to set an accent color for all public pages + ([5c529a8](https://code.castopod.org/adaures/castopod/commit/5c529a83aa6d6147d94e5aee996e6b0ab02f0ce4)) +- simplify podcast page's layout for better ux + ([2c0efc6](https://code.castopod.org/adaures/castopod/commit/2c0efc6563604dd067be88cfc9ddd88a01745e64)) +- **soundbites:** add soundbite list and creation forms with audio-clipper + component + ([de19317](https://code.castopod.org/adaures/castopod/commit/de19317138a2106deb825c1eed7dda036ed7dac3)) +- style file inputs using tailwind's file class + ([8208ab6](https://code.castopod.org/adaures/castopod/commit/8208ab6785aae8c49f78eb9ac8cd53d77ec8e5e5)) +- **themes:** add ViewThemes library to set views in root themes folder + ([7a27676](https://code.castopod.org/adaures/castopod/commit/7a276764e6a1ee3619d9d3488f6163215db75338)) +- **themes:** set different default banner per theme + ([11c916f](https://code.castopod.org/adaures/castopod/commit/11c916fe433eb749ac32230c48e256057564cbb0)) +- **themes:** set generic css variables for colors to enable instance themes + ([a746a78](https://code.castopod.org/adaures/castopod/commit/a746a781b4bfc78209cf8302c6d7bb3cb452e446)) +- toggle podcast sidebar on smaller screens + ([f0205ec](https://code.castopod.org/adaures/castopod/commit/f0205ec274414e881cba40d6776126f05eaee583)) +- **transcript:** parse srt subtitles into json file + add max file size info + below audio file input + ([0098761](https://code.castopod.org/adaures/castopod/commit/00987610a068c8d6cdd4421ea16585fa037eb61a)) +- **ui:** create ViewComponents library to enable building class and view files + components + ([94872f2](https://code.castopod.org/adaures/castopod/commit/94872f2338e6025c2f3770be256160838dae9003)) +- update analytics so to meet IABv2 requirements + ([03e23a2](https://code.castopod.org/adaures/castopod/commit/03e23a28bf9b1b73fba55352c36a8cd6cc8ae729)), + closes [#10](https://code.castopod.org/adaures/castopod/issues/10) +- update pine colors + create charts components + ([a50abc1](https://code.castopod.org/adaures/castopod/commit/a50abc138d4997b564e3065b37504cda5ce62da6)) +- **users:** add myth-auth to handle users crud + add admin gateway only + accessible by login + ([c63a077](https://code.castopod.org/adaures/castopod/commit/c63a077618c61b4cde7f25ffc650a4b0e1495f44)), + closes [#11](https://code.castopod.org/adaures/castopod/issues/11) +- **ux:** remove admin dashboard and redirect directly to podcast list + ([27c48b8](https://code.castopod.org/adaures/castopod/commit/27c48b8fa930b33e5e15f0c8685e468e857ca9cd)) +- **video-clip:** add video-clip page with video preview + logs + ([42538dd](https://code.castopod.org/adaures/castopod/commit/42538dd7577be0ffe59b4fdfadbd76cc89e5ef30)) +- **video-clip:** generate video clips in the bg using a cron job + add video + clip page + tidy up UI + ([db0e427](https://code.castopod.org/adaures/castopod/commit/db0e4272bd6d307c562e1f961d2747cb62de0f35)) +- **video-clips:** add dimensions for portrait and squared formats + ([3af404d](https://code.castopod.org/adaures/castopod/commit/3af404da3dd1901c78cc7e1778fc225f6716207d)) +- **video-clips:** add new themes + add castopod logo as a watermark + ([1d1490b](https://code.castopod.org/adaures/castopod/commit/1d1490b06a1f5ecb10b3b98a72efc55d09c10944)) +- **video-clips:** add route for scheduled video clips + list video clips with + status + ([2065ebb](https://code.castopod.org/adaures/castopod/commit/2065ebbee5e3d0f890ac90b55ca984f1d62a184c)) +- **video-clips:** allow episodeNumbering text to stand in the indent of + episodeTitle paragraph + ([71a063d](https://code.castopod.org/adaures/castopod/commit/71a063dac311cb21639801fbae6af7c5106c2699)) +- **video-clips:** generate a 16:9 video using ffmpeg + ([35aa7ea](https://code.castopod.org/adaures/castopod/commit/35aa7ea5d9a339b3e6f745137282268d69fe2231)) +- **video-clips:** generate subtitles clip using transcript json to have + subtitles accross video + ([3ce07e4](https://code.castopod.org/adaures/castopod/commit/3ce07e455d171e29be30d8ad45055510eb8d363c)) +- **video-clips:** replace hardcoded colors with config's theme colors + ([e462abf](https://code.castopod.org/adaures/castopod/commit/e462abf6d660e41d2170c52caf45704008de58e9)) +- **vite:** add vite config to decouple it from CI_ENVIRONMENT + ([8721719](https://code.castopod.org/adaures/castopod/commit/8721719cd7cf32e94823541eafaba1e9309355a8)) +- write id3v2 tags to episode's audio file + ([4651d01](https://code.castopod.org/adaures/castopod/commit/4651d01a84ff3ea8433a8ae26cfd750a1ec9e88d)) + +### Performance Improvements + +- **cache:** update CI4 to use cache's deleteMatching method + ([54b84f9](https://code.castopod.org/adaures/castopod/commit/54b84f96843af13f579fea49102c8c2ef81b0a54)) +- **cache:** use deleteMatching method to prevent forgetting cached elements in + models + ([76afc0c](https://code.castopod.org/adaures/castopod/commit/76afc0cfa2feb087697bae4bc138e4956873dd62)) +- defer javascript + lazy load images for faster page loads + ([f0685e4](https://code.castopod.org/adaures/castopod/commit/f0685e44799dfb494592ff97841c0ae035381db8)) +- **docker:** add redis caching service for development + ([05ace8c](https://code.castopod.org/adaures/castopod/commit/05ace8cff2ef02d19abd40097ac5546dca6a54ca)) + +### Reverts + +- **install:** redirect to install in homepage if no database was set + ([73f094d](https://code.castopod.org/adaures/castopod/commit/73f094daf26a8cf75e39ebff1eeb7f9039276312)) +- set deprecated config options back in App config + ([433745f](https://code.castopod.org/adaures/castopod/commit/433745f194c73407999b207090478563283876a5)) +- **soundbites:** remove soundbite table from episode's public page + ([5dc0f19](https://code.castopod.org/adaures/castopod/commit/5dc0f19656de0d764f627d6ae78a9e306c901835)) +- use basic input file for episodes audio files instead of button for better UX + ([d5f22fb](https://code.castopod.org/adaures/castopod/commit/d5f22fbb38c43d9b37df401eff655958a57cb40a)) + +### BREAKING CHANGES + +- **analytics:** analytics_podcasts_by_player table and analytics_podcasts + procedure were updated + # [1.0.0-beta.24](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.23...v1.0.0-beta.24) (2022-10-14) ### Bug Fixes diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 57da0daf..b1291ae9 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,128 +1,162 @@ -# Contributor Covenant Code of Conduct +# Contributor Covenant 3.0 Code of Conduct ## Our Pledge -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity and -orientation. +We pledge to make our community welcoming, safe, and equitable for all. -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. +We are committed to fostering an environment that respects and promotes the +dignity, rights, and contributions of all individuals, regardless of +characteristics including race, ethnicity, caste, color, age, physical +characteristics, neurodiversity, disability, sex or gender, gender identity or +expression, sexual orientation, language, philosophy or religion, national or +social origin, socio-economic position, level of education, or other status. The +same privileges of participation are extended to everyone who participates in +good faith and in accordance with this Covenant. -## Our Standards +## Encouraged Behaviors -Examples of behavior that contributes to a positive environment for our -community include: +While acknowledging differences in social norms, we all strive to meet our +community's expectations for positive behavior. We also understand that our +words and actions may be interpreted differently than we intend based on +culture, background, or native language. -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -- Focusing on what is best not just for us as individuals, but for the overall - community +With these considerations in mind, we agree to behave mindfully toward each +other and act in ways that center our shared values, including: -Examples of unacceptable behavior include: +1. Respecting the **purpose of our community**, our activities, and our ways of + gathering. +2. Engaging **kindly and honestly** with others. +3. Respecting **different viewpoints** and experiences. +4. **Taking responsibility** for our actions and contributions. +5. Gracefully giving and accepting **constructive feedback**. +6. Committing to **repairing harm** when it occurs. +7. Behaving in other ways that promote and sustain the **well-being of our + community**. -- The use of sexualized language or imagery, and sexual attention or advances of - any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email address, - without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting +## Restricted Behaviors -## Enforcement Responsibilities +We agree to restrict the following behaviors in our community. Instances, +threats, and promotion of these behaviors are violations of this Code of +Conduct. -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. +1. **Harassment.** Violating explicitly expressed boundaries or engaging in + unnecessary personal attention after any clear request to stop. +2. **Character attacks.** Making insulting, demeaning, or pejorative comments + directed at a community member or group of people. +3. **Stereotyping or discrimination.** Characterizing anyone’s personality or + behavior on the basis of immutable identities or traits. +4. **Sexualization.** Behaving in a way that would generally be considered + inappropriately intimate in the context or purpose of the community. +5. **Violating confidentiality**. Sharing or acting on someone's personal or + private information without their permission. +6. **Endangerment.** Causing, encouraging, or threatening violence or other harm + toward any person or group. +7. Behaving in other ways that **threaten the well-being** of our community. -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. +### Other Restrictions + +1. **Misleading identity.** Impersonating someone else for any reason, or + pretending to be someone else to evade enforcement actions. +2. **Failing to credit sources.** Not properly crediting the sources of content + you contribute. +3. **Promotional materials**. Sharing marketing or other commercial content in a + way that is outside the norms of the community. +4. **Irresponsible communication.** Failing to responsibly present content which + includes, links or describes any other restricted behaviors. + +## Reporting an Issue + +Tensions can occur between community members even when they are trying their +best to collaborate. Not every conflict represents a code of conduct violation, +and this Code of Conduct reinforces encouraged behaviors and norms that can help +avoid conflicts and minimize harm. + +When an incident does occur, it is important to report it promptly. To report a +possible violation, email us at [abuse@castopod.org](mailto:abuse@castopod.org). + +Community Moderators take reports of violations seriously and will make every +effort to respond in a timely manner. They will investigate all reports of code +of conduct violations, reviewing messages, logs, and recordings, or interviewing +witnesses and other participants. Community Moderators will keep investigation +and enforcement actions as transparent as possible while prioritizing safety and +confidentiality. In order to honor these values, enforcement actions are carried +out in private with the involved parties, but communicating to the whole +community may be part of a mutually agreed upon resolution. + +## Addressing and Repairing Harm + +If an investigation by the Community Moderators finds that this Code of Conduct +has been violated, the following enforcement ladder may be used to determine how +best to repair harm, based on the incident's impact on the individuals involved +and the community as a whole. Depending on the severity of a violation, lower +rungs on the ladder may be skipped. + +1. Warning + 1. Event: A violation involving a single incident or series of incidents. + 2. Consequence: A private, written warning from the Community Moderators. + 3. Repair: Examples of repair include a private written apology, + acknowledgement of responsibility, and seeking clarification on + expectations. +2. Temporarily Limited Activities + 1. Event: A repeated incidence of a violation that previously resulted in a + warning, or the first incidence of a more serious violation. + 2. Consequence: A private, written warning with a time-limited cooldown + period designed to underscore the seriousness of the situation and give + the community members involved time to process the incident. The cooldown + period may be limited to particular communication channels or interactions + with particular community members. + 3. Repair: Examples of repair may include making an apology, using the + cooldown period to reflect on actions and impact, and being thoughtful + about re-entering community spaces after the period is over. +3. Temporary Suspension + 1. Event: A pattern of repeated violation which the Community Moderators have + tried to address with warnings, or a single serious violation. + 2. Consequence: A private written warning with conditions for return from + suspension. In general, temporary suspensions give the person being + suspended time to reflect upon their behavior and possible corrective + actions. + 3. Repair: Examples of repair include respecting the spirit of the + suspension, meeting the specified conditions for return, and being + thoughtful about how to reintegrate with the community when the suspension + is lifted. +4. Permanent Ban + 1. Event: A pattern of repeated code of conduct violations that other steps + on the ladder have failed to resolve, or a violation so serious that the + Community Moderators determine there is no way to keep the community safe + with this person as a member. + 2. Consequence: Access to all community spaces, tools, and communication + channels is removed. In general, permanent bans should be rarely used, + should have strong reasoning behind them, and should only be resorted to + if working through other remedies has failed to change the behavior. + 3. Repair: There is no possible repair in cases of this severity. + +This enforcement ladder is intended as a guideline. It does not limit the +ability of Community Managers to use their discretion and judgment, in keeping +with the best interests of our community. ## Scope This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed +an individual is officially representing the community in public or other +spaces. Examples of representing our community include using an official email +address, posting via an official social media account, or acting as an appointed representative at an online or offline event. -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -[abuse@castopod.org](mailto:abuse@castopod.org). All complaints will be reviewed -and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -community. - ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. +This Code of Conduct is adapted from the Contributor Covenant, version 3.0, +permanently available at +[https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/). -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). +Contributor Covenant is stewarded by the Organization for Ethical Source and +licensed under CC BY-SA 4.0. To view a copy of this license, visit +[https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/) -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. +For answers to common questions about Contributor Covenant, see the FAQ at +[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). +Translations are provided at +[https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). +Additional enforcement and community guideline resources can be found at +[https://www.contributor-covenant.org/resources](https://www.contributor-covenant.org/resources). +The enforcement ladder was inspired by the work of +[Mozilla’s code of conduct team](https://github.com/mozilla/inclusion). diff --git a/docs/src/ar/contributing/setup-development.md b/CONTRIBUTING-DEV.md similarity index 63% rename from docs/src/ar/contributing/setup-development.md rename to CONTRIBUTING-DEV.md index 6aafb50b..907d3ce1 100644 --- a/docs/src/ar/contributing/setup-development.md +++ b/CONTRIBUTING-DEV.md @@ -1,8 +1,3 @@ ---- -title: Development setup -sidebarDepth: 3 ---- - # Setup your development environment ## Introduction @@ -10,7 +5,7 @@ sidebarDepth: 3 Castopod is a web app based on the `php` framework [CodeIgniter 4](https://codeigniter.com). -We use [Docker](https://www.docker.com/) quickly setup a dev environment. A +We use [Docker](https://www.docker.com/) to quickly setup a dev environment. A `docker-compose.yml` and `Dockerfile` are included in the project's root folder to help you kickstart your contribution. @@ -21,9 +16,9 @@ to help you kickstart your contribution. ### 1. Pre-requisites -0. Install [docker](https://docs.docker.com/get-docker). +0. Install [Docker](https://docs.docker.com/get-docker). -1. Clone Castopod project by running: +1. Clone the Castopod repository by running: ```bash git clone https://code.castopod.org/adaures/castopod.git @@ -34,7 +29,7 @@ to help you kickstart your contribution. ```ini CI_ENVIRONMENT="development" - # If set to development, you must run `npm run dev` to start the static assets server + # If set to development, you must run `pnpm run dev` to start the static assets server vite.environment="development" # By default, this is set to true in the app config. @@ -43,7 +38,6 @@ to help you kickstart your contribution. app.forceGlobalSecureRequests=false app.baseURL="http://localhost:8080/" - app.mediaBaseURL="http://localhost:8080/" admin.gateway="cp-admin" auth.gateway="cp-auth" @@ -52,25 +46,43 @@ to help you kickstart your contribution. database.default.database="castopod" database.default.username="castopod" database.default.password="castopod" + database.default.DBPrefix="dev_" + + analytics.salt="DEV_ANALYTICS_SALT" cache.handler="redis" - cache.redis.host = "redis" + cache.redis.host="redis" # You may not want to use redis as your cache handler # Comment/remove the two lines above and uncomment # the next line for file caching. + # ----------------------- #cache.handler="file" + + ###################################### + # Media config + ###################################### + media.baseURL="http://localhost:8080/" + + # S3 + # Uncomment to store s3 objects using adobe/s3mock service + # ----------------------- + #media.fileManager="s3" + #media.s3.bucket="castopod" + #media.s3.endpoint="http://172.31.0.6:9090/" + #media.s3.pathStyleEndpoint=true ``` - > _NB._ You can tweak your environment by setting more environment variables - > in your custom `.env` file. See the `env` for examples or the + > [!NOTE] + > You can tweak your environment by setting more environment variables in + > your custom `.env` file. See the `env` for examples or the > [CodeIgniter4 User Guide](https://codeigniter.com/user_guide/index.html) > for more info. -3. (for docker desktop) Add the repository you've cloned to docker desktop's +3. (for Docker desktop) Add the repository you've cloned to Docker desktop's `Settings` > `Resources` > `File Sharing` -### 2. (recommended) Develop inside the app Container with VSCode +### 2. (recommended) Develop inside the app container with VSCode If you're working in VSCode, you can take advantage of the `.devcontainer/` folder. It defines a development environment (dev container) with preinstalled @@ -84,16 +96,16 @@ required services will be loaded automagically! 🪄 > The VSCode window will reload inside the dev container. Expect several > minutes during first load as it is building all necessary services. - **Note**: The dev container will start by running Castopod's php server. + **Note**: The dev container will start by running Castopod's PHP server. During development, you will have to start [Vite](https://vitejs.dev)'s dev server for compiling the typescript code and styles: ```bash # run Vite dev server - npm run dev + pnpm run dev ``` - If there is any issue with the php server not running, you can restart them + If there is any issue with the PHP server not running, you can restart them using the following commands: ```bash @@ -113,15 +125,15 @@ required services will be loaded automagically! 🪄 # Composer is installed composer -V - # npm is installed - npm -v + # pnpm is installed + pnpm -v # git is installed git version ``` For more info, see -[VSCode Remote Containers](https://code.visualstudio.com/docs/remote/containers) +[Developing inside a Container](https://code.visualstudio.com/docs/devcontainers/containers) ### 3. Start hacking @@ -132,9 +144,12 @@ more insights. To see your changes, go to: -- `http://localhost:8080/` for the Castopod app -- `http://localhost:8888/` for the phpmyadmin interface: +- `http://localhost:8080/` for the Castopod website +- `http://localhost:8080/cp-admin` for the Castopod admin: + - email: **admin@castopod.local** + - password: **castopod** +- `http://localhost:8888/` for the phpmyadmin interface: - username: **castopod** - password: **castopod** @@ -142,9 +157,9 @@ To see your changes, go to: You do not wish to use the VSCode devcontainer? No problem! -1. Start docker containers manually: +1. Start the Docker containers manually: - Go to project's root folder and run: + Go to the project's root folder and run: ```bash # starts all services declared in docker-compose.yml file @@ -159,7 +174,7 @@ You do not wish to use the VSCode devcontainer? No problem! ``` - > The `docker-compose up -d` command will boot 4 containers in the + > The `docker-compose up -d` command will boot 5 containers in the > background: > > - `castopod_app`: a php based container with Castopod requirements @@ -170,6 +185,7 @@ You do not wish to use the VSCode devcontainer? No problem! > persistent data > - `castopod_phpmyadmin`: a phpmyadmin server to visualize the mariadb > database. + > - `castopod_s3`: a mock s3 server to work on the s3 fileManager 2. Run any command inside the containers by prefixing them with `docker-compose run --rm app`: @@ -181,8 +197,8 @@ You do not wish to use the VSCode devcontainer? No problem! # use Composer docker-compose run --rm app composer -V - # use npm - docker-compose run --rm app npm -v + # use pnpm + docker-compose run --rm app pnpm -v # use git docker-compose run --rm app git version @@ -200,57 +216,46 @@ You do not wish to use the VSCode devcontainer? No problem! composer install ``` - ::: info Note + > [!NOTE] + > The php dependencies aren't included in the repository. Composer will check + > the `composer.json` and `composer.lock` files to download the packages with + > the right versions. The dependencies will live under the `vendor/` folder. + > For more info, check out the + > [Composer documentation](https://getcomposer.org/doc/). - The php dependencies aren't included in the repository. Composer will check - the `composer.json` and `composer.lock` files to download the packages with - the right versions. The dependencies will live under the `vendor/` folder. - For more info, check out the - [Composer documentation](https://getcomposer.org/doc/). - - ::: - -2. Install javascript dependencies with [npm](https://www.npmjs.com/) +2. Install JavaScript dependencies with [pnpm](https://pnpm.io/) ```bash - npm install + pnpm install ``` - ::: info Note - - The javascript dependencies aren't included in the repository. Npm will check - the `package.json` and `package.lock` files to download the packages with the - right versions. The dependencies will live under the `node_module` folder. - For more info, check out the [NPM documentation](https://docs.npmjs.com/). - - ::: + > [!NOTE] + > The JavaScript dependencies aren't included in the repository. Pnpm will + > check the `package.json` and `pnpm-lock.yaml` files to download the + > packages with the right versions. The dependencies will live under the + > `node_module` folder. For more info, check out the + > [PNPM documentation](https://pnpm.io/motivation). 3. Generate static assets: ```bash # build all static assets at once - npm run build:static + pnpm run build:static # build specific assets - npm run build:icons - npm run build:svg + pnpm run build:icons + pnpm run build:svg ``` - ::: info Note - - The static assets generated live under the `public/assets` folder, it - includes javascript, styles, images, fonts, icons and svg files. - - ::: + > [!NOTE] + > The static assets generated live under the `public/assets` folder, it + > includes JavaScript, styles, images, fonts, icons and svg files. ### Initialize and populate database -::: tip Tip - -You may skip this section if you go through the install wizard (go to -`/cp-install`). - -::: +> [!TIP] +> You may skip this section if you go through the install wizard (go to +> `/cp-install`). 1. Build the database with the migrate command: @@ -270,7 +275,7 @@ You may skip this section if you go through the install wizard (go to ```bash # Populates all required data - php spark db:seed AppSeeder + php spark db:seed DevSeeder ``` You may choose to add data separately: @@ -282,21 +287,11 @@ You may skip this section if you go through the install wizard (go to # Populates all Languages php spark db:seed LanguageSeeder - # Populates all podcasts platforms - php spark db:seed PlatformSeeder - - # Populates all Authentication data (roles definition…) - php spark db:seed AuthSeeder - ``` - -3. (optionnal) Populate the database with test data: - - - Populate test data (login: admin / password: AGUehL3P) - - ```bash - php spark db:seed TestSeeder + # Adds a superadmin with [admin@castopod.local / castopod] credentials + php spark db:seed DevSuperadminSeeder ``` +3. (optional) Populate the database with test data: - Populate with fake podcast analytics: ```bash @@ -309,11 +304,6 @@ You may skip this section if you go through the install wizard (go to php spark db:seed FakeWebsiteAnalyticsSeeder ``` - TestSeeder will add an active superadmin user with the following credentials: - - - username: **admin** - - password: **AGUehL3P** - ### Useful docker / docker-compose commands - Monitor the app container: @@ -322,13 +312,13 @@ You may skip this section if you go through the install wizard (go to docker-compose logs --tail 50 --follow --timestamps app ``` -- Interact with redis server using included redis-cli command: +- Interact with the Redis server using included redis-cli command: ```bash docker exec -it castopod_redis redis-cli ``` -- Monitor the redis container: +- Monitor the Redis container: ```bash docker-compose logs --tail 50 --follow --timestamps redis @@ -364,28 +354,52 @@ docker-compose down docker-compose build app ``` -Check [docker](https://docs.docker.com/engine/reference/commandline/docker/) and +Check [Docker](https://docs.docker.com/engine/reference/commandline/docker/) and [docker-compose](https://docs.docker.com/compose/reference/) documentations for more insights. +### Updating Documentation + +Castopod's documentation is written in Markdown and uses the Astro Starlight +framework. To update Castopod's documentation, including the Getting Started +guide and User Guide: + +1. Change directories to the `docs` directory and install the dependencies: + + ```bash + cd docs/ + pnpm i + ``` + +2. Start the documentation development server: + + ```bash + pnpm run dev --host + ``` + +3. The documentation development server runs on port 4321. In your browser visit + `http://localhost:4321/docs`. If the page displays a 404 Not Found error, + click on the Castopod logo in the upper left hand corner of the page and the + documentation should load. + +4. Edit the Markdown files with your documentation updates. The Astro Starlight + development server will automatically update each time you save a change. + ## Known issues ### Allocation failed - JavaScript heap out of memory -This happens when running `npm install`. +This happens when running `pnpm install`. 👉 By default, docker might not have access to enough RAM. Allocate more memory -and run `npm install` again. +and run `pnpm install` again. ### (Linux) Files created inside container are attributed to root locally You may use Linux user namespaces to fix this on your machine: -::: info Note - -Replace "username" with your local username - -::: +> [!NOTE] +> Replace "username" with your local username 1. Go to `/etc/docker/daemon.json` and add: @@ -409,7 +423,7 @@ Replace "username" with your local username username:100000:65536 ``` -3. Restart docker: +3. Restart Docker: ```bash sudo systemctl restart docker diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 21c69f78..a3468537 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,167 @@ -# Contributing guidelines +# Contributing to Castopod -You may find the contributing guidelines in the -[Castopod documentation website](https://docs.castopod.org/contributing/guidelines.html). +Love Castopod and want to help? Thanks so much, there's something to do for +everybody! + +> [!NOTE] +> Castopod follows the [all contributors](https://allcontributors.org/) +> specification in an effort to **recognize any kind of contribution**, not just +> code! +> If you've made a contribution and do not appear in the +> [contributors](../index.md#contributors-✨) list, please +> [let us know](../index.md#contact) so we can correct our mistake! 🙂 + +Please take a moment to review this document in order to make the contribution +process easy and effective for everyone involved. + +Following these guidelines helps to communicate that you respect the time of the +developers managing and developing this open source project. In return, they +should reciprocate that respect in addressing your issue or assessing patches +and features. + +## Translating Castopod + +We use [Crowdin](https://translate.castopod.org/) to manage translation files +for [Castopod](https://code.castopod.org/), the +[documentation](https://docs.castopod.org/) and the +[landing](https://castopod.org/) websites. + +Whether you'd like to correct a translation error, validate new translations or +include your language to Castopod, head into the +[crowdin project](https://translate.castopod.org/) to get started. + +> [!NOTE] +> To prevent degrading user experience, new languages are included to Castopod +> when they reach a certain threshold (~90%). + +## Using the issue tracker + +The [issue tracker](https://code.castopod.org/adaures/castopod/-/issues) is the +preferred channel for [bug reports](#bug-reports), +[features requests](#feature-requests) and +[submitting pull requests](#pull-requests). + +## ⚠️ Security issues and vulnerabilities + +If you encounter any security issue or vulnerability in the Castopod source, +please contact us directly by email at +[security@castopod.org](mailto:security@castopod.org) + +## Bug reports + +A bug is a _demonstrable problem_ that is caused by the code in the repository. +Good bug reports are extremely helpful - thank you! + +Guidelines for bug reports: + +1. **Use the issue search** — check if the issue has already been + reported. + +2. **Check if the issue has been fixed** — try to reproduce it using the + latest `main` branch in the repository. + +3. **Isolate the problem** — ideally create a + [reduced test case](https://css-tricks.com/reduced-test-cases/) and a live + example. + +A good bug report shouldn't leave others needing to chase you up for more +information. Please try to be as detailed as possible in your report. What is +your environment? What steps will reproduce the issue? What browser(s) and OS +experience the problem? What would you expect to be the outcome? All these +details will help people to fix any potential bugs. + +> [!NOTE] +> [Issue templates](https://docs.gitlab.com/ee/user/project/description_templates.html#using-the-templates) have +> been created for this project. You may use them to help you follow those +> guidelines. + +## Feature requests + +Feature requests are welcome. But take a moment to find out whether your idea +fits with the scope and aims of the project. It's up to _you_ to make a strong +case to convince the project's developers of the merits of this feature. Please +provide as much detail and context as possible. + +## Pull requests + +Good pull requests - patches, improvements, new features - are a fantastic help. +They should remain focused in scope and avoid containing unrelated commits. + +**Please ask first** before embarking on any significant pull request (e.g. +implementing features, refactoring code, porting to a different language), +otherwise you risk spending a lot of time working on something that the +project's developers might not want to merge into the project. + +Please adhere to the coding conventions used throughout a project (indentation, +accurate comments, etc.) and any other requirements (such as test coverage). + +Adhering to the following process is the best way to get your work included in +the project: + +1. [Fork](https://docs.gitlab.com/ee/user/project/repository/forking_workflow.html) + the project, clone your fork, and configure the remotes: + + ```bash + # Clone your fork of the repo into the current directory + git clone https://code.castopod.org//castopod.git + + # Navigate to the newly cloned directory + cd castopod + + # Assign the original repo to a remote called "upstream" + git remote add upstream https://code.castopod.org/adaures/castopod.git + ``` + +2. If you cloned a while ago, get the latest changes from upstream: + + ```bash + git checkout main + git pull upstream main + ``` + +3. Create a new topic branch (off the `main` branch) to contain your feature, + change, or fix: + + ```bash + git checkout -b + ``` + +4. Commit your changes in logical chunks. Please adhere to these + [git commit message guidelines](https://conventionalcommits.org/) or your + code is unlikely be merged into the main project. Use Git's + [interactive rebase](https://help.github.com/articles/about-git-rebase/) + feature to tidy up your commits before making them public. + +5. Locally merge (or rebase) the upstream dev branch into your topic branch: + + ```bash + git pull [--rebase] upstream main + ``` + +6. Push your topic branch up to your fork: + + ```bash + git push origin + ``` + +7. [Open a Pull Request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html#new-merge-request-from-a-fork) + with a clear title and description. + +> [!IMPORTANT] +> By submitting a patch, you agree to allow the project owners to license your +> work under the terms of the +> [GNU AGPLv3](https://code.castopod.org/adaures/castopod/-/blob/develop/LICENSE.md). + +## Collaborating guidelines + +There are few basic rules to ensure high quality of the project: + +- Before merging, a PR requires at least two approvals from the collaborators + unless it's an architectural change, a large feature, etc. If it is, then at + least 50% of the core team have to agree to merge it, with every team member + having a full veto right. (i.e. every single one can block any PR) +- A PR should remain open for at least two days before merging (does not apply + for trivial contributions like fixing a typo). This way everyone has enough + time to look into it. + +You are always welcome to discuss and propose improvements to this guideline. diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index b9c083c2..97175ffd 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -18,9 +18,9 @@ Javascript dependencies can be found in the [package.json](./package.json) file. ([Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL)) - [RemixIcon](https://remixicon.com/) ([Apache License 2.0](https://github.com/Remix-Design/RemixIcon/blob/master/License)) -- [OPAWG/User agent list](https://github.com/opawg/user-agents) +- [OPAWG/User agent list](https://github.com/opawg/user-agents-v2) ([by Open Podcast Analytics Working Group](https://github.com/opawg)) - ([MIT license](https://github.com/opawg/user-agents/blob/master/LICENSE)) + ([MIT license](https://github.com/opawg/user-agents-v2/blob/master/LICENSE)) - [OPAWG/podcast-rss-useragents](https://github.com/opawg/podcast-rss-useragents) ([by Open Podcast Analytics Working Group](https://github.com/opawg)) ([MIT license](https://github.com/opawg/podcast-rss-useragents/blob/master/LICENSE)) diff --git a/README.md b/README.md index 89b6c508..6460720c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ @@ -15,16 +15,11 @@ Castopod is a free and open-source podcast hosting solution made for podcasters who want engage and interact with their audience. -> **Status** -> -> Castopod is currently in **beta** but already quite stable and used by -> podcasters around the world! - ## Getting started -To get started with Castopod, you may -[check out the documentation](https://docs.castopod.org/), everything should be -there! +Castopod comes pre-packaged with all the required static assets and +dependencies, you may download and install it by checking out the +[getting started page](https://castopod.org/getting-started/)! ## Security issues and vulnerabilities @@ -36,12 +31,9 @@ please contact us directly by email at Contributions are always welcome! -See the -[contribution guidelines](https://docs.castopod.org/contributing/guidelines) for -ways to get started. +See the [contribution guidelines](./CONTRIBUTING.md) for ways to get started. -> **Note** -> +> [!Important] > **Any** contribution made on a repository other than > [the original repository](https://code.castopod.org/adaures/castopod) will not > be accepted. @@ -55,63 +47,76 @@ Thanks goes to these wonderful people - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Yassine Doghri

💻 🐛 📖 👀 🚧 🖋 🎨 ️️️️♿️ 🌍 💬 🧑‍🏫 🚇 🤔 📆 📝

Benjamin Bellamy

💻 🐛 👀 🖋 🌍 💬 🚇 🤔 📝 📆 📢

Ola Hneini

💻 👀 📖 🚧 💬 🤔

Romain de Laage

💻 🚇 📖 🌍 🤔

Lyonel Bernard

🐛 💬 🔊 🤔

Christopher Lagonick-Weitzel

🐛 💬 🔊 🤔

Ernesto Acosta

🐛 🔊 🌍 💬 🤔

Bastien Luneteau

💻 🐛

Cécile Ricordeau

🎨

Patryk Miś

🌍

Marcin Lewandowski

🐛 🤔

Sebastian Janik

💻

Patryk Karczmarczyk

💻

denis d

🐛 🤔

Douglas Kastle

🐛 🤔

cExplorer

🐛 🌍

ImaCrea

🐛 🤔

Jonas S

💻

LEFEBVRE Yann

🐛

Sebastian Späth

🐛 🤔

rocky III

🐛

Hermann Josef Eckl

🐛

Delhaye Cyrille

🐛 🤔

João Leandro

🌍 🤔

Angelos Chouvardas

🌍

Eivind

🌍

Ewen

🌍 🤔

forght

🌍

glottis0q

🌍

ButterflyOfFire

🌍

Lucian I. Last

🌍

LuuzViir

🌍

CTHTC

🌍

Russian Retro

🌍

Marek L'ach

🌍

GunChleoc

🌍

GabiSnow

🌍

bendaha

🌍

Samuel Roland

🌍

Dimitri Regnier

🤔

irithys

🌍

Sergi

🌍

ghose (XoseM)

🌍
Yassine Doghri
Yassine Doghri

💻 🐛 📖 👀 🚧 🖋 🎨 ️️️️♿️ 🌍 💬 🧑‍🏫 🚇 🤔 📆 📝
Benjamin Bellamy
Benjamin Bellamy

💻 🐛 👀 🖋 🌍 💬 🚇 🤔 📝 📆 📢
Ola Hneini
Ola Hneini

💻 👀 📖 🚧 💬 🤔
Romain de Laage
Romain de Laage

💻 🚇 📖 🌍 🤔
Lyonel Bernard
Lyonel Bernard

🐛 💬 🔊 🤔
Christopher Lagonick-Weitzel
Christopher Lagonick-Weitzel

🐛 💬 🔊 🤔
Ernesto Acosta
Ernesto Acosta

🐛 🔊 🌍 💬 🤔
Ewen
Ewen

🌍 🤔 💻
Bastien Luneteau
Bastien Luneteau

💻 🐛
Cécile Ricordeau
Cécile Ricordeau

🎨
Patryk Miś
Patryk Miś

🌍
Marcin Lewandowski
Marcin Lewandowski

🐛 🤔
Sebastian Janik
Sebastian Janik

💻
Patryk Karczmarczyk
Patryk Karczmarczyk

💻
denis d
denis d

🐛 🤔
Douglas Kastle
Douglas Kastle

🐛 🤔
cExplorer
cExplorer

🐛 🌍
ImaCrea
ImaCrea

🐛 🤔
Jonas S
Jonas S

💻
LEFEBVRE Yann
LEFEBVRE Yann

🐛
Sebastian Späth
Sebastian Späth

🐛 🤔
rocky III
rocky III

🐛
Hermann Josef Eckl
Hermann Josef Eckl

🐛
Delhaye Cyrille
Delhaye Cyrille

🐛 🤔
João Leandro
João Leandro

🌍 🤔
Angelos Chouvardas
Angelos Chouvardas

🌍
Eivind
Eivind

🌍
forght
forght

🌍
glottis0q
glottis0q

🌍
ButterflyOfFire
ButterflyOfFire

🌍
Lucian I. Last
Lucian I. Last

🌍
LuuzViir
LuuzViir

🌍
CTHTC
CTHTC

🌍
Russian Retro
Russian Retro

🌍
Marek L'ach
Marek L'ach

🌍
GunChleoc
GunChleoc

🌍
GabiSnow
GabiSnow

🌍
bendaha
bendaha

🌍
Samuel Roland
Samuel Roland

🌍
Dimitri Regnier
Dimitri Regnier

🤔
irithys
irithys

🌍
Sergi
Sergi

🌍
Andreas Olsson
Andreas Olsson

🌍
leonfrom
leonfrom

🌍
agentcobra
agentcobra

🌍
Alessandro
Alessandro

🌍
liimee
liimee

🌍
Ahmed Sabouni
Ahmed Sabouni

🌍
KrzysztofDomanczyk
KrzysztofDomanczyk

💻
Guy Martin
Guy Martin

🐛 💻
Paul Cutler
Paul Cutler

📖 💬 🤔
Nate Ritter
Nate Ritter

💻
@@ -136,7 +141,7 @@ Alternatively, you can follow us on social media platforms to get news about Castopod: - [podlibre.social](https://podlibre.social/@Castopod) (Mastodon instance) -- [Twitter](https://twitter.com/castopod) +- [Bluesky](https://bsky.app/profile/castopod.org) - [LinkedIn](https://linkedin.com/company/castopod) - [Facebook](https://www.facebook.com/castopod) @@ -150,10 +155,10 @@ backers. If you'd like to help, please consider - Netlify + Ad Aures - NLnet Logo + NLnet Logo diff --git a/app/.htaccess b/app/.htaccess index f24db0ac..e64d9494 100644 --- a/app/.htaccess +++ b/app/.htaccess @@ -1,6 +1,2 @@ - - Require all denied - - - Deny from all - + Require all denied + Deny from all diff --git a/app/Commands/EpisodesComputeDownloads.php b/app/Commands/EpisodesComputeDownloads.php new file mode 100644 index 00000000..1bdc7b2d --- /dev/null +++ b/app/Commands/EpisodesComputeDownloads.php @@ -0,0 +1,50 @@ +builder() + ->select('episodes.id as id, IFNULL(SUM(ape.hits),0) as downloads_count') + ->join('analytics_podcasts_by_episode ape', 'episodes.id=ape.episode_id', 'left') + ->groupBy('episodes.id'); + + $episodeModel2 = new EpisodeModel(); + $episodeModel2->builder() + ->setQueryAsData($query) + ->onConstraint('id') + ->updateBatch(); + } +} diff --git a/app/Common.php b/app/Common.php index 1f3b17c2..89981c0d 100644 --- a/app/Common.php +++ b/app/Common.php @@ -2,8 +2,6 @@ declare(strict_types=1); -use Config\Services; -use Config\View; use ViewThemes\Theme; /** @@ -14,7 +12,7 @@ use ViewThemes\Theme; * This can be looked at as a `master helper` file that is loaded early on, and may also contain additional functions * that you'd like to use throughout your entire application * - * @link: https://codeigniter4.github.io/CodeIgniter4/ + * @see: https://codeigniter.com/user_guide/extending/common.html */ if (! function_exists('view')) { @@ -29,12 +27,17 @@ if (! function_exists('view')) { */ function view(string $name, array $data = [], array $options = []): string { + if (array_key_exists('theme', $options)) { + Theme::setTheme($options['theme']); + } + $path = Theme::path(); /** @var CodeIgniter\View\View $renderer */ $renderer = single_service('renderer', $path); - $saveData = config(View::class)->saveData; + $saveData = config('View') + ->saveData; if (array_key_exists('saveData', $options)) { $saveData = (bool) $options['saveData']; @@ -45,40 +48,3 @@ if (! function_exists('view')) { ->render($name, $options, $saveData); } } - -if (! function_exists('lang')) { - /** - * A convenience method to translate a string or array of them and format the result with the intl extension's - * MessageFormatter. - * - * Overwritten to include an escape parameter (escaped by default). - * - * @param array $args - * - * @return string|string[] - */ - function lang(string $line, array $args = [], ?string $locale = null, bool $escape = true): string | array - { - $language = Services::language(); - - // Get active locale - $activeLocale = $language->getLocale(); - - if ($locale && $locale !== $activeLocale) { - $language->setLocale($locale); - } - - $line = $language->getLine($line, $args); - if (! $locale) { - return $escape ? esc($line) : $line; - } - - if ($locale === $activeLocale) { - return $escape ? esc($line) : $line; - } - - // Reset to active locale - $language->setLocale($activeLocale); - return $escape ? esc($line) : $line; - } -} diff --git a/app/Config/App.php b/app/Config/App.php index 6aaeccae..404ff5a0 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace Config; use CodeIgniter\Config\BaseConfig; -use CodeIgniter\Session\Handlers\FileHandler; +use Override; class App extends BaseConfig { @@ -14,38 +14,34 @@ class App extends BaseConfig * Base Site URL * -------------------------------------------------------------------------- * - * URL to your CodeIgniter root. Typically this will be your base URL, + * URL to your CodeIgniter root. Typically, this will be your base URL, * WITH a trailing slash: * - * http://example.com/ - * - * If this is not set then CodeIgniter will try guess the protocol, domain - * and path to your installation. However, you should always configure this - * explicitly and never rely on auto-guessing, especially in production - * environments. + * E.g., http://example.com/ */ public string $baseURL = 'http://localhost:8080/'; /** - * -------------------------------------------------------------------------- - * Media Base URL - * -------------------------------------------------------------------------- + * Allowed Hostnames in the Site URL other than the hostname in the baseURL. + * If you want to accept multiple Hostnames, set this. * - * URL to your media root. Typically this will be your base URL, - * WITH a trailing slash: + * E.g., + * When your site URL ($baseURL) is 'http://example.com/', and your site + * also accepts 'http://media.example.com/' and 'http://accounts.example.com/': + * ['media.example.com', 'accounts.example.com'] * - * http://cdn.example.com/ + * @var list */ - public string $mediaBaseURL = 'http://localhost:8080/'; + public array $allowedHostnames = []; /** * -------------------------------------------------------------------------- * Index File * -------------------------------------------------------------------------- * - * Typically this will be your index.php file, unless you've renamed it to - * something else. If you are using mod_rewrite to remove the page set this - * variable so that it is blank. + * Typically, this will be your `index.php` file, unless you've renamed it to + * something else. If you have configured your web server to remove this file + * from your site URIs, set this variable to an empty string. */ public string $indexPage = ''; @@ -55,17 +51,41 @@ class App extends BaseConfig * -------------------------------------------------------------------------- * * This item determines which server global should be used to retrieve the - * URI string. The default setting of 'REQUEST_URI' works for most servers. + * URI string. The default setting of 'REQUEST_URI' works for most servers. * If your links do not seem to work, try one of the other delicious flavors: * - * 'REQUEST_URI' Uses $_SERVER['REQUEST_URI'] - * 'QUERY_STRING' Uses $_SERVER['QUERY_STRING'] - * 'PATH_INFO' Uses $_SERVER['PATH_INFO'] + * 'REQUEST_URI': Uses $_SERVER['REQUEST_URI'] + * 'QUERY_STRING': Uses $_SERVER['QUERY_STRING'] + * 'PATH_INFO': Uses $_SERVER['PATH_INFO'] * * WARNING: If you set this to 'PATH_INFO', URIs will always be URL-decoded! */ public string $uriProtocol = 'REQUEST_URI'; + /* + *-------------------------------------------------------------------------- + * Allowed URL Characters + *-------------------------------------------------------------------------- + * + * This lets you specify which characters are permitted within your URLs. + * When someone tries to submit a URL with disallowed characters they will + * get a warning message. + * + * As a security measure you are STRONGLY encouraged to restrict URLs to + * as few characters as possible. + * + * By default, only these are allowed: `a-z 0-9~%.:_-` + * + * Set an empty string to allow all characters -- but only if you are insane. + * + * The configured value is actually a regular expression character group + * and it will be used as: '/\A[]+\z/iu' + * + * DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!! + * + */ + public string $permittedURIChars = 'a-z 0-9~%.:_\-@'; + /** * -------------------------------------------------------------------------- * Default Locale @@ -99,9 +119,23 @@ class App extends BaseConfig * by the application in descending order of priority. If no match is * found, the first locale will be used. * - * @var string[] + * IncomingRequest::setLocale() also uses this list. + * + * @var list */ - public array $supportedLocales = ['en', 'fr', 'pl', 'de', 'pt-BR', 'nn-NO', 'es', 'zh-Hans', 'ca']; + public array $supportedLocales = [ + 'en', + 'fr', + 'pl', + 'de', + 'pt-br', + 'nn-no', + 'es', + 'zh-hans', + 'ca', + 'br', + 'sr-latn', + ]; /** * -------------------------------------------------------------------------- @@ -110,6 +144,9 @@ class App extends BaseConfig * * The default timezone that will be used in your application to display * dates with the date helper, and can be retrieved through app_timezone() + * + * @see https://www.php.net/manual/en/timezones.php for list of timezones + * supported by PHP. */ public string $appTimezone = 'UTC'; @@ -133,170 +170,10 @@ class App extends BaseConfig * If true, this will force every request made to this application to be * made via a secure connection (HTTPS). If the incoming request is not * secure, the user will be redirected to a secure version of the page - * and the HTTP Strict Transport Security header will be set. + * and the HTTP Strict Transport Security (HSTS) header will be set. */ public bool $forceGlobalSecureRequests = true; - /** - * -------------------------------------------------------------------------- - * Session Driver - * -------------------------------------------------------------------------- - * - * The session storage driver to use: - * - `CodeIgniter\Session\Handlers\FileHandler` - * - `CodeIgniter\Session\Handlers\DatabaseHandler` - * - `CodeIgniter\Session\Handlers\MemcachedHandler` - * - `CodeIgniter\Session\Handlers\RedisHandler` - */ - public string $sessionDriver = FileHandler::class; - - /** - * -------------------------------------------------------------------------- - * Session Cookie Name - * -------------------------------------------------------------------------- - * - * The session cookie name, must contain only [0-9a-z_-] characters - */ - public string $sessionCookieName = 'ci_session'; - - /** - * -------------------------------------------------------------------------- - * Session Expiration - * -------------------------------------------------------------------------- - * - * The number of SECONDS you want the session to last. - * Setting to 0 (zero) means expire when the browser is closed. - */ - public int $sessionExpiration = 7200; - - /** - * -------------------------------------------------------------------------- - * Session Save Path - * -------------------------------------------------------------------------- - * - * The location to save sessions to and is driver dependent. - * - * For the 'files' driver, it's a path to a writable directory. - * WARNING: Only absolute paths are supported! - * - * For the 'database' driver, it's a table name. - * Please read up the manual for the format with other session drivers. - * - * IMPORTANT: You are REQUIRED to set a valid save path! - */ - public string $sessionSavePath = WRITEPATH . 'session'; - - /** - * -------------------------------------------------------------------------- - * Session Match IP - * -------------------------------------------------------------------------- - * - * Whether to match the user's IP address when reading the session data. - * - * WARNING: If you're using the database driver, don't forget to update - * your session table's PRIMARY KEY when changing this setting. - */ - public bool $sessionMatchIP = false; - - /** - * -------------------------------------------------------------------------- - * Session Time to Update - * -------------------------------------------------------------------------- - * - * How many seconds between CI regenerating the session ID. - */ - public int $sessionTimeToUpdate = 300; - - /** - * -------------------------------------------------------------------------- - * Session Regenerate Destroy - * -------------------------------------------------------------------------- - * - * Whether to destroy session data associated with the old session ID - * when auto-regenerating the session ID. When set to FALSE, the data - * will be later deleted by the garbage collector. - */ - public bool $sessionRegenerateDestroy = false; - - /** - * -------------------------------------------------------------------------- - * Cookie Prefix - * -------------------------------------------------------------------------- - * - * Set a cookie name prefix if you need to avoid collisions. - * - * @deprecated use Config\Cookie::$prefix property instead. - */ - public string $cookiePrefix = ''; - - /** - * -------------------------------------------------------------------------- - * Cookie Domain - * -------------------------------------------------------------------------- - * - * Set to `.your-domain.com` for site-wide cookies. - * - * @deprecated use Config\Cookie::$domain property instead. - */ - public string $cookieDomain = ''; - - /** - * -------------------------------------------------------------------------- - * Cookie Path - * -------------------------------------------------------------------------- - * - * Typically will be a forward slash. - * - * @deprecated use Config\Cookie::$path property instead. - */ - public string $cookiePath = '/'; - - /** - * -------------------------------------------------------------------------- - * Cookie Secure - * -------------------------------------------------------------------------- - * - * Cookie will only be set if a secure HTTPS connection exists. - * - * @deprecated use Config\Cookie::$secure property instead. - */ - public bool $cookieSecure = false; - - /** - * -------------------------------------------------------------------------- - * Cookie HttpOnly - * -------------------------------------------------------------------------- - * - * Cookie will only be accessible via HTTP(S) (no JavaScript). - * - * @deprecated use Config\Cookie::$httponly property instead. - */ - public bool $cookieHTTPOnly = true; - - /** - * -------------------------------------------------------------------------- - * Cookie SameSite - * -------------------------------------------------------------------------- - * - * Configure cookie SameSite setting. Allowed values are: - * - None - * - Lax - * - Strict - * - '' - * - * Alternatively, you can use the constant names: - * - `Cookie::SAMESITE_NONE` - * - `Cookie::SAMESITE_LAX` - * - `Cookie::SAMESITE_STRICT` - * - * Defaults to `Lax` for compatibility with modern browsers. Setting `''` - * (empty string) means default SameSite attribute set by browsers (`Lax`) - * will be set on cookies. If set to `None`, `$cookieSecure` must also be set. - * - * @deprecated `Config\Cookie` $samesite property is used. - */ - public string $cookieSameSite = 'Lax'; - /** * -------------------------------------------------------------------------- * Reverse Proxy IPs @@ -304,103 +181,21 @@ class App extends BaseConfig * * If your server is behind a reverse proxy, you must whitelist the proxy * IP addresses from which CodeIgniter should trust headers such as - * HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify + * X-Forwarded-For or Client-IP in order to properly identify * the visitor's IP address. * - * You can use both an array or a comma-separated list of proxy addresses, - * as well as specifying whole subnets. Here are a few examples: + * You need to set a proxy IP address or IP address with subnets and + * the HTTP header for the client IP address. * - * Comma-separated: '10.0.1.200,192.168.5.0/24' - * Array: ['10.0.1.200', '192.168.5.0/24'] + * Here are some examples: + * [ + * '10.0.1.200' => 'X-Forwarded-For', + * '192.168.5.0/24' => 'X-Real-IP', + * ] * - * @var string|string[] + * @var array|string */ - public string | array $proxyIPs = ''; - - /** - * -------------------------------------------------------------------------- - * CSRF Token Name - * -------------------------------------------------------------------------- - * - * The token name. - * - * @deprecated Use `Config\Security` $tokenName property instead of using this property. - */ - public string $CSRFTokenName = 'csrf_test_name'; - - /** - * -------------------------------------------------------------------------- - * CSRF Header Name - * -------------------------------------------------------------------------- - * - * The header name. - * - * @deprecated Use `Config\Security` $headerName property instead of using this property. - */ - public string $CSRFHeaderName = 'X-CSRF-TOKEN'; - - /** - * -------------------------------------------------------------------------- - * CSRF Cookie Name - * -------------------------------------------------------------------------- - * - * The cookie name. - * - * @deprecated Use `Config\Security` $cookieName property instead of using this property. - */ - public string $CSRFCookieName = 'csrf_cookie_name'; - - /** - * -------------------------------------------------------------------------- - * CSRF Expire - * -------------------------------------------------------------------------- - * - * The number in seconds the token should expire. - * - * @deprecated Use `Config\Security` $expire property instead of using this property. - */ - public int $CSRFExpire = 7200; - - /** - * -------------------------------------------------------------------------- - * CSRF Regenerate - * -------------------------------------------------------------------------- - * - * Regenerate token on every submission? - * - * @deprecated Use `Config\Security` $regenerate property instead of using this property. - */ - public bool $CSRFRegenerate = true; - - /** - * -------------------------------------------------------------------------- - * CSRF Redirect - * -------------------------------------------------------------------------- - * - * Redirect to previous page with error on failure? - * - * @deprecated Use `Config\Security` $redirect property instead of using this property. - */ - public bool $CSRFRedirect = true; - - /** - * -------------------------------------------------------------------------- - * CSRF SameSite - * -------------------------------------------------------------------------- - * - * Setting for CSRF SameSite cookie token. Allowed values are: - * - None - * - Lax - * - Strict - * - '' - * - * Defaults to `Lax` as recommended in this link: - * - * @see https://portswigger.net/web-security/csrf/samesite-cookies - * - * @deprecated Use `Config\Security` $samesite property instead of using this property. - */ - public string $CSRFSameSite = 'Lax'; + public $proxyIPs = []; /** * -------------------------------------------------------------------------- @@ -420,14 +215,6 @@ class App extends BaseConfig */ public bool $CSPEnabled = false; - /** - * -------------------------------------------------------------------------- - * Media root folder - * -------------------------------------------------------------------------- - * Defines the root folder for media files storage - */ - public string $mediaRoot = 'media'; - /** * -------------------------------------------------------------------------- * Instance / Site Config @@ -444,7 +231,7 @@ class App extends BaseConfig */ public array $siteIcon = [ 'ico' => '/favicon.ico', - '64' => '/icon-64.png', + '64' => '/icon-64.png', '180' => '/icon-180.png', '192' => '/icon-192.png', '512' => '/icon-512.png', @@ -457,5 +244,43 @@ class App extends BaseConfig */ public ?int $storageLimit = null; + /** + * Bandwidth limit (per month) in Gigabytes + */ + public ?int $bandwidthLimit = null; + public ?string $legalNoticeURL = null; + + /** + * AuthToken Config Constructor + */ + public function __construct() + { + parent::__construct(); + + if (is_string($this->proxyIPs)) { + $array = json_decode($this->proxyIPs, true); + if (is_array($array)) { + $this->proxyIPs = $array; + } + } + } + + /** + * Override parent initEnvValue() to allow for direct setting to array properties values from ENV + * + * In order to set array properties via ENV vars we need to set the property to a string value first. + * + * @param mixed $property + */ + #[Override] + protected function initEnvValue(&$property, string $name, string $prefix, string $shortPrefix): void + { + // if attempting to set property from ENV, first set to empty string + if ($name === 'proxyIPs' && $this->getEnvValue($name, $prefix, $shortPrefix) !== null) { + $property = ''; + } + + parent::initEnvValue($property, $name, $prefix, $shortPrefix); + } } diff --git a/app/Config/Autoload.php b/app/Config/Autoload.php index f314d3d2..3fd8e175 100644 --- a/app/Config/Autoload.php +++ b/app/Config/Autoload.php @@ -27,42 +27,39 @@ class Autoload extends AutoloadConfig * their location on the file system. These are used by the autoloader * to locate files the first time they have been instantiated. * - * The '/app' and '/system' directories are already mapped for you. - * you may change the name of the 'App' namespace if you wish, + * The 'Config' (APPPATH . 'Config') and 'CodeIgniter' (SYSTEMPATH) are + * already mapped for you. + * + * You may change the name of the 'App' namespace if you wish, * but this should be done prior to creating any namespaced classes, * else you will need to modify all of those classes for this to work. * - * Prototype: - * - * $psr4 = [ - * 'CodeIgniter' => SYSTEMPATH, - * 'App' => APPPATH - * ]; - * - * @var array + * @var array|string> */ public $psr4 = [ - APP_NAMESPACE => APPPATH, - 'Modules' => ROOTPATH . 'modules/', - 'Modules\Admin' => ROOTPATH . 'modules/Admin/', - 'Modules\Auth' => ROOTPATH . 'modules/Auth/', - 'Modules\Analytics' => ROOTPATH . 'modules/Analytics/', - 'Modules\Install' => ROOTPATH . 'modules/Install/', - 'Modules\Fediverse' => ROOTPATH . 'modules/Fediverse/', - 'Modules\WebSub' => ROOTPATH . 'modules/WebSub/', - 'Modules\Api\Rest\V1' => ROOTPATH . 'modules/Api/Rest/V1', + APP_NAMESPACE => APPPATH, + 'Modules' => ROOTPATH . 'modules/', + 'Modules\Admin' => ROOTPATH . 'modules/Admin/', + 'Modules\Analytics' => ROOTPATH . 'modules/Analytics/', + 'Modules\Api\Rest\V1' => ROOTPATH . 'modules/Api/Rest/V1', + 'Modules\Auth' => ROOTPATH . 'modules/Auth/', + 'Modules\Fediverse' => ROOTPATH . 'modules/Fediverse/', + 'Modules\Install' => ROOTPATH . 'modules/Install/', + 'Modules\Media' => ROOTPATH . 'modules/Media/', + 'Modules\MediaClipper' => ROOTPATH . 'modules/MediaClipper/', + 'Modules\Platforms' => ROOTPATH . 'modules/Platforms/', + 'Modules\Plugins' => ROOTPATH . 'modules/Plugins/', + 'Modules\PodcastImport' => ROOTPATH . 'modules/PodcastImport/', 'Modules\PremiumPodcasts' => ROOTPATH . 'modules/PremiumPodcasts/', - 'Config' => APPPATH . 'Config/', - 'ViewComponents' => APPPATH . 'Libraries/ViewComponents/', - 'ViewThemes' => APPPATH . 'Libraries/ViewThemes/', - 'MediaClipper' => APPPATH . 'Libraries/MediaClipper/', - 'Vite' => APPPATH . 'Libraries/Vite/', - 'Themes' => ROOTPATH . 'themes', + 'Modules\Update' => ROOTPATH . 'modules/Update/', + 'Modules\WebSub' => ROOTPATH . 'modules/WebSub/', + 'Themes' => ROOTPATH . 'themes', + 'ViewComponents' => APPPATH . 'Libraries/ViewComponents/', + 'ViewThemes' => APPPATH . 'Libraries/ViewThemes/', ]; /** * ------------------------------------------------------------------- - * Class Map * ------------------------------------------------------------------- * The class map provides a map of class names and their exact * location on the drive. Classes loaded in this manner will have @@ -89,12 +86,25 @@ class Autoload extends AutoloadConfig * or for loading functions. * * Prototype: - * ``` + * * $files = [ * '/path/to/my/file.php', * ]; - * ``` - * @var array + * + * @var list */ - public $files = [APPPATH . 'Libraries/ViewComponents/Helpers/view_components_helper.php']; + public $files = []; + + /** + * ------------------------------------------------------------------- + * Helpers + * ------------------------------------------------------------------- + * Prototype: + * $helpers = [ + * 'form', + * ]; + * + * @var list + */ + public $helpers = ['auth', 'setting', 'plugins']; } diff --git a/app/Config/Boot/development.php b/app/Config/Boot/development.php index 8193a56a..e131d316 100644 --- a/app/Config/Boot/development.php +++ b/app/Config/Boot/development.php @@ -9,8 +9,10 @@ declare(strict_types=1); * In development, we want to show as many errors as possible to help * make sure they don't make it to production. And save us hours of * painful debugging. + * + * If you set 'display_errors' to '1', CI4's detailed error report will show. */ -error_reporting(-1); +error_reporting(E_ALL); ini_set('display_errors', '1'); /** diff --git a/app/Config/Boot/production.php b/app/Config/Boot/production.php index 4cf210d8..9d22b60a 100644 --- a/app/Config/Boot/production.php +++ b/app/Config/Boot/production.php @@ -8,9 +8,13 @@ declare(strict_types=1); * -------------------------------------------------------------------------- * Don't show ANY in production environments. Instead, let the system catch * it and display a generic error message. + * + * If you set 'display_errors' to '1', CI4's detailed error report will show. */ +error_reporting(E_ALL & ~E_DEPRECATED); +// If you want to suppress more types of errors. +// error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED); ini_set('display_errors', '0'); -error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED); /** * -------------------------------------------------------------------------- diff --git a/app/Config/Boot/testing.php b/app/Config/Boot/testing.php index 56c22a9c..46e757da 100644 --- a/app/Config/Boot/testing.php +++ b/app/Config/Boot/testing.php @@ -2,6 +2,12 @@ declare(strict_types=1); +/* + * The environment testing is reserved for PHPUnit testing. It has special + * conditions built into the framework at various places to assist with that. + * You can’t use it for your development. + */ + /** * -------------------------------------------------------------------------- * ERROR DISPLAY @@ -10,7 +16,7 @@ declare(strict_types=1); * make sure they don't make it to production. And save us hours of * painful debugging. */ -error_reporting(-1); +error_reporting(E_ALL); ini_set('display_errors', '1'); /** diff --git a/app/Config/CURLRequest.php b/app/Config/CURLRequest.php index a0484ca7..4dbb7afa 100644 --- a/app/Config/CURLRequest.php +++ b/app/Config/CURLRequest.php @@ -8,6 +8,19 @@ use CodeIgniter\Config\BaseConfig; class CURLRequest extends BaseConfig { + /** + * -------------------------------------------------------------------------- + * CURLRequest Share Connection Options + * -------------------------------------------------------------------------- + * + * Share connection options between requests. + * + * @var list + * + * @see https://www.php.net/manual/en/curl.constants.php#constant.curl-lock-data-connect + */ + public array $shareConnectionOptions = [CURL_LOCK_DATA_CONNECT, CURL_LOCK_DATA_DNS]; + /** * -------------------------------------------------------------------------- * CURLRequest Share Options @@ -18,5 +31,5 @@ class CURLRequest extends BaseConfig * If true, all the options won't be reset between requests. * It may cause an error request with unnecessary headers. */ - public bool $shareOptions = true; + public bool $shareOptions = false; } diff --git a/app/Config/Cache.php b/app/Config/Cache.php index 4266fb6a..bbf812f9 100644 --- a/app/Config/Cache.php +++ b/app/Config/Cache.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Config; +use CodeIgniter\Cache\CacheInterface; +use CodeIgniter\Cache\Handlers\ApcuHandler; use CodeIgniter\Cache\Handlers\DummyHandler; use CodeIgniter\Cache\Handlers\FileHandler; use CodeIgniter\Cache\Handlers\MemcachedHandler; @@ -35,37 +37,6 @@ class Cache extends BaseConfig */ public string $backupHandler = 'dummy'; - /** - * -------------------------------------------------------------------------- - * Cache Directory Path - * -------------------------------------------------------------------------- - * - * The path to where cache files should be stored, if using a file-based - * system. - * - * @deprecated Use the driver-specific variant under $file - */ - public string $storePath = WRITEPATH . 'cache/'; - - /** - * -------------------------------------------------------------------------- - * Cache Include Query String - * -------------------------------------------------------------------------- - * - * Whether to take the URL query string into consideration when generating - * output cache files. Valid options are: - * - * false = Disabled - * true = Enabled, take all query parameters into account. - * Please be aware that this may result in numerous cache - * files generated for the same page over and over again. - * array('q') = Enabled, but only take into account the specified list - * of query parameters. - * - * @var boolean|string[] - */ - public bool | array $cacheQueryString = false; - /** * -------------------------------------------------------------------------- * Key Prefix @@ -97,6 +68,7 @@ class Cache extends BaseConfig * A string of reserved characters that will not be allowed in keys or tags. * Strings that violate this restriction will cause handlers to throw. * Default: {}()/\@: + * * Note: The default set is required for PSR-6 compliance. */ public string $reservedCharacters = '{}()/\@:'; @@ -105,32 +77,34 @@ class Cache extends BaseConfig * -------------------------------------------------------------------------- * File settings * -------------------------------------------------------------------------- + * * Your file storage preferences can be specified below, if you are using * the File driver. * - * @var array + * @var array{storePath?: string, mode?: int} */ public array $file = [ 'storePath' => WRITEPATH . 'cache/', - 'mode' => 0640, + 'mode' => 0640, ]; /** * ------------------------------------------------------------------------- * Memcached settings * ------------------------------------------------------------------------- + * * Your Memcached servers can be specified below, if you are using * the Memcached drivers. * * @see https://codeigniter.com/user_guide/libraries/caching.html#memcached * - * @var array + * @var array{host?: string, port?: int, weight?: int, raw?: bool} */ public array $memcached = [ - 'host' => '127.0.0.1', - 'port' => 11211, + 'host' => '127.0.0.1', + 'port' => 11211, 'weight' => 1, - 'raw' => false, + 'raw' => false, ]; /** @@ -140,14 +114,24 @@ class Cache extends BaseConfig * Your Redis server can be specified below, if you are using * the Redis or Predis drivers. * - * @var array + * @var array{ + * host?: string, + * password?: string|null, + * port?: int, + * timeout?: int, + * async?: bool, + * persistent?: bool, + * database?: int + * } */ public array $redis = [ - 'host' => '127.0.0.1', - 'password' => null, - 'port' => 6379, - 'timeout' => 0, - 'database' => 0, + 'host' => '127.0.0.1', + 'password' => null, + 'port' => 6379, + 'timeout' => 0, + 'async' => false, // specific to Predis and ignored by the native Redis extension + 'persistent' => false, + 'database' => 0, ]; /** @@ -158,14 +142,58 @@ class Cache extends BaseConfig * This is an array of cache engine alias' and class names. Only engines * that are listed here are allowed to be used. * - * @var array + * @var array> */ public array $validHandlers = [ - 'dummy' => DummyHandler::class, - 'file' => FileHandler::class, + 'apcu' => ApcuHandler::class, + 'dummy' => DummyHandler::class, + 'file' => FileHandler::class, 'memcached' => MemcachedHandler::class, - 'predis' => PredisHandler::class, - 'redis' => RedisHandler::class, - 'wincache' => WincacheHandler::class, + 'predis' => PredisHandler::class, + 'redis' => RedisHandler::class, + 'wincache' => WincacheHandler::class, ]; + + /** + * -------------------------------------------------------------------------- + * Web Page Caching: Cache Include Query String + * -------------------------------------------------------------------------- + * + * Whether to take the URL query string into consideration when generating + * output cache files. Valid options are: + * + * false = Disabled + * true = Enabled, take all query parameters into account. + * Please be aware that this may result in numerous cache + * files generated for the same page over and over again. + * ['q'] = Enabled, but only take into account the specified list + * of query parameters. + * + * @var bool|list + */ + public $cacheQueryString = false; + + /** + * -------------------------------------------------------------------------- + * Web Page Caching: Cache Status Codes + * -------------------------------------------------------------------------- + * + * HTTP status codes that are allowed to be cached. Only responses with + * these status codes will be cached by the PageCache filter. + * + * Default: [] - Cache all status codes (backward compatible) + * + * Recommended: [200] - Only cache successful responses + * + * You can also use status codes like: + * [200, 404, 410] - Cache successful responses and specific error codes + * [200, 201, 202, 203, 204] - All 2xx successful responses + * + * WARNING: Using [] may cache temporary error pages (404, 500, etc). + * Consider restricting to [200] for production applications to avoid + * caching errors that should be temporary. + * + * @var list + */ + public array $cacheStatusCodes = []; } diff --git a/app/Config/Colors.php b/app/Config/Colors.php index 83ac145e..49618892 100644 --- a/app/Config/Colors.php +++ b/app/Config/Colors.php @@ -14,136 +14,136 @@ class Colors extends BaseConfig public array $themes = [ /* Castopod's brand color */ 'pine' => [ - 'accent-base' => [174, 100, 29], - 'accent-hover' => [172, 100, 17], - 'accent-muted' => [131, 100, 12], + 'accent-base' => [174, 100, 29], + 'accent-hover' => [172, 100, 17], + 'accent-muted' => [131, 100, 12], 'accent-contrast' => [0, 0, 100], 'heading-foreground' => [172, 100, 17], 'heading-background' => [111, 64, 94], - 'background-elevated' => [0, 0, 100], - 'background-base' => [173, 44, 96], + 'background-elevated' => [0, 0, 100], + 'background-base' => [173, 44, 96], 'background-navigation' => [172, 100, 17], - 'background-header' => [172, 100, 17], - 'background-highlight' => [111, 64, 94], - 'background-backdrop' => [0, 0, 50], + 'background-header' => [172, 100, 17], + 'background-highlight' => [111, 64, 94], + 'background-backdrop' => [0, 0, 50], - 'border-subtle' => [111, 42, 86], - 'border-contrast' => [0, 0, 0], + 'border-subtle' => [111, 42, 86], + 'border-contrast' => [0, 0, 0], 'border-navigation' => [131, 100, 12], - 'text-base' => [158, 8, 3], + 'text-base' => [158, 8, 3], 'text-muted' => [172, 8, 38], ], /* Red / Rose color */ 'crimson' => [ - 'accent-base' => [350, 87, 61], - 'accent-hover' => [348, 75, 40], - 'accent-muted' => [348, 73, 32], + 'accent-base' => [350, 87, 61], + 'accent-hover' => [348, 75, 40], + 'accent-muted' => [348, 73, 32], 'accent-contrast' => [0, 0, 100], 'heading-foreground' => [348, 73, 32], 'heading-background' => [344, 79, 96], - 'background-elevated' => [0, 0, 100], - 'background-base' => [350, 44, 96], - 'background-header' => [348, 75, 40], + 'background-elevated' => [0, 0, 100], + 'background-base' => [350, 44, 96], + 'background-header' => [348, 75, 40], 'background-highlight' => [344, 79, 96], - 'background-backdrop' => [0, 0, 50], + 'background-backdrop' => [0, 0, 50], - 'border-subtle' => [348, 42, 86], + 'border-subtle' => [348, 42, 86], 'border-contrast' => [0, 0, 0], - 'text-base' => [340, 8, 3], + 'text-base' => [340, 8, 3], 'text-muted' => [345, 8, 38], ], /* Blue color */ 'lake' => [ - 'accent-base' => [194, 100, 44], - 'accent-hover' => [194, 100, 22], - 'accent-muted' => [195, 100, 11], + 'accent-base' => [194, 100, 44], + 'accent-hover' => [194, 100, 22], + 'accent-muted' => [195, 100, 11], 'accent-contrast' => [0, 0, 100], 'heading-foreground' => [194, 100, 22], 'heading-background' => [195, 100, 92], - 'background-elevated' => [0, 0, 100], - 'background-base' => [196, 44, 96], - 'background-header' => [194, 100, 22], + 'background-elevated' => [0, 0, 100], + 'background-base' => [196, 44, 96], + 'background-header' => [194, 100, 22], 'background-highlight' => [195, 100, 92], - 'background-backdrop' => [0, 0, 50], + 'background-backdrop' => [0, 0, 50], - 'border-subtle' => [195, 42, 86], + 'border-subtle' => [195, 42, 86], 'border-contrast' => [0, 0, 0], - 'text-base' => [194, 8, 3], + 'text-base' => [194, 8, 3], 'text-muted' => [195, 8, 38], ], /* Orange color */ 'amber' => [ - 'accent-base' => [17, 100, 57], - 'accent-hover' => [17, 100, 35], - 'accent-muted' => [17, 100, 24], + 'accent-base' => [17, 100, 57], + 'accent-hover' => [17, 100, 35], + 'accent-muted' => [17, 100, 24], 'accent-contrast' => [0, 0, 100], 'heading-foreground' => [17, 100, 35], 'heading-background' => [17, 100, 89], - 'background-elevated' => [0, 0, 100], - 'background-base' => [15, 44, 96], - 'background-header' => [17, 100, 35], + 'background-elevated' => [0, 0, 100], + 'background-base' => [15, 44, 96], + 'background-header' => [17, 100, 35], 'background-highlight' => [17, 100, 89], - 'background-backdrop' => [0, 0, 50], + 'background-backdrop' => [0, 0, 50], - 'border-subtle' => [17, 42, 86], + 'border-subtle' => [17, 42, 86], 'border-contrast' => [0, 0, 0], - 'text-base' => [15, 8, 3], + 'text-base' => [15, 8, 3], 'text-muted' => [17, 8, 38], ], /* Violet color */ 'jacaranda' => [ - 'accent-base' => [254, 72, 52], - 'accent-hover' => [254, 73, 30], - 'accent-muted' => [254, 71, 19], + 'accent-base' => [254, 72, 52], + 'accent-hover' => [254, 73, 30], + 'accent-muted' => [254, 71, 19], 'accent-contrast' => [0, 0, 100], 'heading-foreground' => [254, 73, 30], 'heading-background' => [254, 73, 84], - 'background-elevated' => [0, 0, 100], - 'background-base' => [253, 44, 96], - 'background-header' => [254, 73, 30], + 'background-elevated' => [0, 0, 100], + 'background-base' => [253, 44, 96], + 'background-header' => [254, 73, 30], 'background-highlight' => [254, 88, 91], - 'background-backdrop' => [0, 0, 50], + 'background-backdrop' => [0, 0, 50], - 'border-subtle' => [254, 42, 86], + 'border-subtle' => [254, 42, 86], 'border-contrast' => [0, 0, 0], - 'text-base' => [253, 8, 3], + 'text-base' => [253, 8, 3], 'text-muted' => [254, 8, 38], ], /* Black color */ 'onyx' => [ - 'accent-base' => [240, 17, 2], - 'accent-hover' => [240, 17, 17], - 'accent-muted' => [240, 17, 17], + 'accent-base' => [240, 17, 2], + 'accent-hover' => [240, 17, 17], + 'accent-muted' => [240, 17, 17], 'accent-contrast' => [0, 0, 100], 'heading-foreground' => [240, 17, 17], 'heading-background' => [240, 17, 94], - 'background-elevated' => [0, 0, 100], - 'background-base' => [240, 17, 96], - 'background-header' => [240, 12, 17], + 'background-elevated' => [0, 0, 100], + 'background-base' => [240, 17, 96], + 'background-header' => [240, 12, 17], 'background-highlight' => [240, 17, 94], - 'background-backdrop' => [0, 0, 50], + 'background-backdrop' => [0, 0, 50], - 'border-subtle' => [240, 17, 86], + 'border-subtle' => [240, 17, 86], 'border-contrast' => [0, 0, 0], - 'text-base' => [240, 8, 3], + 'text-base' => [240, 8, 3], 'text-muted' => [240, 8, 38], ], ]; diff --git a/app/Config/Constants.php b/app/Config/Constants.php index b52f7ea4..e23c0e0d 100644 --- a/app/Config/Constants.php +++ b/app/Config/Constants.php @@ -11,7 +11,7 @@ declare(strict_types=1); | | NOTE: this constant is updated upon release with Continuous Integration. */ -defined('CP_VERSION') || define('CP_VERSION', '1.0.0-beta.24'); +defined('CP_VERSION') || define('CP_VERSION', '2.0.0-next.3'); /* | -------------------------------------------------------------------- @@ -24,10 +24,23 @@ defined('CP_VERSION') || define('CP_VERSION', '1.0.0-beta.24'); | classes should use. | | NOTE: changing this will require manually modifying the - | existing namespaces of App\* namespaced-classes. + | existing namespaces of App* namespaced-classes. */ defined('APP_NAMESPACE') || define('APP_NAMESPACE', 'App'); +/* + | -------------------------------------------------------------------- + | Plugins Path + | -------------------------------------------------------------------- + | + | This defines the folder in which plugins will live. + */ +defined('PLUGINS_PATH') || + define('PLUGINS_PATH', ROOTPATH . 'plugins' . DIRECTORY_SEPARATOR); + +defined('PLUGINS_KEY_PATTERN') || + define('PLUGINS_KEY_PATTERN', '[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9]([_.-]?[a-z0-9]+)*'); + /* | -------------------------------------------------------------------------- | Composer Path @@ -91,18 +104,3 @@ defined('EXIT_USER_INPUT') || define('EXIT_USER_INPUT', 7); // invalid user inpu defined('EXIT_DATABASE') || define('EXIT_DATABASE', 8); // database error defined('EXIT__AUTO_MIN') || define('EXIT__AUTO_MIN', 9); // lowest automatically-assigned error code defined('EXIT__AUTO_MAX') || define('EXIT__AUTO_MAX', 125); // highest automatically-assigned error code - -/** - * @deprecated Use \CodeIgniter\Events\Events::PRIORITY_LOW instead. - */ -define('EVENT_PRIORITY_LOW', 200); - -/** - * @deprecated Use \CodeIgniter\Events\Events::PRIORITY_NORMAL instead. - */ -define('EVENT_PRIORITY_NORMAL', 100); - -/** - * @deprecated Use \CodeIgniter\Events\Events::PRIORITY_HIGH instead. - */ -define('EVENT_PRIORITY_HIGH', 10); diff --git a/app/Config/ContentSecurityPolicy.php b/app/Config/ContentSecurityPolicy.php index 301f7731..99fa0b0a 100644 --- a/app/Config/ContentSecurityPolicy.php +++ b/app/Config/ContentSecurityPolicy.php @@ -26,37 +26,77 @@ class ContentSecurityPolicy extends BaseConfig */ public ?string $reportURI = null; + /** + * Specifies a reporting endpoint to which violation reports ought to be sent. + */ + public ?string $reportTo = null; + /** * Instructs user agents to rewrite URL schemes, changing HTTP to HTTPS. This directive is for websites with large * numbers of old URLs that need to be rewritten. */ public bool $upgradeInsecureRequests = false; + // ------------------------------------------------------------------------- + // CSP DIRECTIVES SETTINGS + // NOTE: once you set a policy to 'none', it cannot be further restricted + // ------------------------------------------------------------------------- + /** - * Will default to self if not overridden + * Will default to `'self'` if not overridden * - * @var string|string[]|null + * @var list|string|null */ public string | array | null $defaultSrc = null; /** * Lists allowed scripts' URLs. * - * @var string|string[] + * @var list|string */ public string | array $scriptSrc = 'self'; + /** + * Specifies valid sources for JavaScript + HTML); + + if ($this->title) { + $this->tag('title', esc($this->title)); + } + + if (url_is(route_to('admin') . '*') || url_is(base_url(config('Auth')->gateway) . '*')) { + // restricted admin and auth areas, do not index + $this->meta('robots', 'noindex'); + } else { + // public website, set siteHead hook only there + service('plugins') + ->siteHead($this); + } + + $head = ''; + foreach ($this->tags as $tag) { + if ($tag['value'] === null) { + $head .= <<stringify_attributes($tag['attributes'])}/> + HTML; + } else { + $head .= <<stringify_attributes($tag['attributes'])}>{$tag['value']} + HTML; + } + } + + $head .= $this->rawContent . ''; + + // reset head for next render + $this->title = null; + $this->tags = []; + $this->rawContent = ''; + + return $head; + } + + public function title(string $title): self + { + $this->title = $title; + return $this->meta('title', $title) + ->og('title', $title) + ->twitter('title', $title); + } + + public function description(string $desc): self + { + return $this->meta('description', $desc) + ->og('description', $desc) + ->twitter('description', $desc); + } + + public function image(string $url, string $card = 'summary_large_image'): self + { + return $this->og('image', $url) + ->twitter('card', $card) + ->twitter('image', $url); + } + + public function canonical(string $url): self + { + return $this->tag('link', null, [ + 'rel' => 'canonical', + 'href' => $url, + ]); + } + + public function twitter(string $name, string $value): self + { + $this->meta("twitter:{$name}", $value); + return $this; + } + + /** + * @param array $attributes + */ + public function tag(string $name, ?string $value = null, array $attributes = []): self + { + $this->tags[] = [ + 'name' => $name, + 'value' => $value, + 'attributes' => $attributes, + ]; + + return $this; + } + + public function meta(string $name, string $content): self + { + $this->tag('meta', null, [ + 'name' => $name, + 'content' => $content, + ]); + + return $this; + } + + public function og(string $name, string $content): self + { + $this->meta('og:' . $name, $content); + + return $this; + } + + public function appendRawContent(string $content): self + { + $this->rawContent .= $content; + + return $this; + } + + /** + * @param array $attributes + */ + private function stringify_attributes(array $attributes): string + { + return stringify_attributes($attributes); + } +} diff --git a/app/Libraries/MediaClipper/Config/MediaClipper.php b/app/Libraries/MediaClipper/Config/MediaClipper.php deleted file mode 100644 index e0394165..00000000 --- a/app/Libraries/MediaClipper/Config/MediaClipper.php +++ /dev/null @@ -1,346 +0,0 @@ ->> - */ - public array $formats = [ - 'landscape' => [ - 'width' => 1920, - 'height' => 1080, - 'cover' => [ - 'width' => 480, - 'height' => 480, - 'radius' => 24, - 'x' => 150, - 'y' => 120, - ], - 'quotes' => [ - 'width' => 192, - 'height' => 192, - 'x' => 810, - 'y' => 210, - ], - 'podcastTitle' => [ - 'fontsize' => 20, - 'x' => 150, - 'y' => 620, - 'lineWidth' => 510, - ], - 'episodeTitle' => [ - 'fontsize' => 32, - 'x' => 150, - 'y' => 660, - 'lines' => 3, - 'lineWidth' => 510, - 'lineHeight' => 1.5, - ], - 'episodeNumbering' => [ - 'fontsize' => 18, - 'paddingX' => 10, - 'paddingY' => 5, - 'marginRight' => 10, - ], - 'timestamp' => [ - 'fontsize' => 32, - 'padding' => 10, - 'x' => 1620, - 'y' => 985, - ], - 'watermark' => [ - 'width' => 90, - 'height' => 72, - 'x' => 140, - 'y' => 960, - ], - 'progressbar' => [ - 'height' => 10, - ], - 'soundwaves' => [ - 'width' => 192, - 'height' => 108, - 'rescaleWidth' => 1920, - 'rescaleHeight' => 540, - 'x' => 0, - 'y' => 810, - 'mask' => APPPATH . 'Libraries/MediaClipper/soundwaves-mask-landscape.png', - ], - 'subtitles' => [ - 'fontsize' => 18, - 'marginL' => 180, - 'marginR' => 20, - 'marginV' => 85, - ], - ], - 'portrait' => [ - 'width' => 1080, - 'height' => 1920, - 'cover' => [ - 'width' => 280, - 'height' => 280, - 'radius' => 16, - 'x' => 50, - 'y' => 50, - ], - 'quotes' => [ - 'width' => 256, - 'height' => 256, - 'x' => 40, - 'y' => 520, - ], - 'podcastTitle' => [ - 'fontsize' => 32, - 'x' => 360, - 'y' => 55, - 'lineWidth' => 670, - ], - 'episodeTitle' => [ - 'fontsize' => 42, - 'x' => 360, - 'y' => 110, - 'lines' => 3, - 'lineWidth' => 670, - 'lineHeight' => 1.5, - ], - 'episodeNumbering' => [ - 'fontsize' => 28, - 'paddingX' => 10, - 'paddingY' => 10, - 'marginRight' => 10, - ], - 'timestamp' => [ - 'fontsize' => 48, - 'padding' => 14, - 'x' => 734, - 'y' => 1800, - ], - 'watermark' => [ - 'width' => 120, - 'height' => 96, - 'x' => 130, - 'y' => 1770, - ], - 'progressbar' => [ - 'height' => 10, - ], - 'soundwaves' => [ - 'width' => 54, - 'height' => 96, - 'rescaleWidth' => 1080, - 'rescaleHeight' => 1920, - 'x' => 0, - 'y' => 960, - 'mask' => APPPATH . 'Libraries/MediaClipper/soundwaves-mask-portrait.png', - ], - 'subtitles' => [ - 'fontsize' => 16, - 'marginL' => 40, - 'marginR' => 25, - 'marginV' => 97, - ], - ], - 'squared' => [ - 'width' => 1200, - 'height' => 1200, - 'cover' => [ - 'width' => 200, - 'height' => 200, - 'radius' => 16, - 'x' => 40, - 'y' => 40, - ], - 'quotes' => [ - 'width' => 200, - 'height' => 200, - 'x' => 85, - 'y' => 320, - ], - 'podcastTitle' => [ - 'fontsize' => 28, - 'x' => 260, - 'y' => 50, - 'lines' => 1, - 'lineWidth' => 800, - ], - 'episodeTitle' => [ - 'fontsize' => 36, - 'x' => 260, - 'y' => 90, - 'lines' => 2, - 'lineWidth' => 850, - 'lineHeight' => 1.5, - ], - 'episodeNumbering' => [ - 'fontsize' => 24, - 'paddingX' => 10, - 'paddingY' => 5, - 'marginRight' => 10, - ], - 'timestamp' => [ - 'fontsize' => 48, - 'padding' => 10, - 'x' => 855, - 'y' => 1070, - ], - 'watermark' => [ - 'width' => 120, - 'height' => 96, - 'x' => 130, - 'y' => 1040, - ], - 'progressbar' => [ - 'height' => 10, - ], - 'soundwaves' => [ - 'width' => 60, - 'height' => 60, - 'rescaleWidth' => 1200, - 'rescaleHeight' => 1200, - 'x' => 0, - 'y' => 600, - 'mask' => APPPATH . 'Libraries/MediaClipper/soundwaves-mask-squared.png', - ], - 'subtitles' => [ - 'fontsize' => 20, - 'marginL' => 60, - 'marginR' => 20, - 'marginV' => 98, - ], - ], - ]; - - /** - * @var array> - */ - public array $themes = [ - 'pine' => [ - // Previews must be a HSL colorscheme string - 'preview' => '174 100% 29%', - 'preview-background' => '172 100% 17%', - // arrays are rgb - 'background' => [0, 86, 74], - 'text' => [255, 255, 255], - // subtitle hex color is BGR (Blue, Green, Red), - 'subtitles' => 'FFFFFF', - // quotes image MUST BE black - 'quotes' => [0, 148, 134], - 'episodeNumberingBg' => [0, 61, 11], - 'episodeNumberingText' => [255, 255, 255], - 'progressbar' => '009486', - 'timestampBg' => '00564A', - 'timestampText' => 'FFFFFF', - 'watermarkBg' => '00564A', - 'soundwaves' => [231, 249, 228], - ], - 'crimson' => [ - // Preview must be a HSL colorscheme string - 'preview' => '350 87% 61%', - 'preview-background' => '348 75% 40%', - // arrays are rgb - 'background' => [179, 31, 57], - 'text' => [255, 255, 255], - // subtitle hex color is BGR (Blue, Green, Red), - 'subtitles' => 'FFFFFF', - // quotes image MUST BE black - 'quotes' => [242, 70, 100], - 'episodeNumberingBg' => [152, 16, 43], - 'episodeNumberingText' => [255, 255, 255], - 'progressbar' => 'F24664', - 'timestampBg' => 'B31F39', - 'timestampText' => 'FFFFFF', - 'watermarkBg' => 'B31F39', - 'soundwaves' => [253, 206, 215], - ], - 'lake' => [ - // Preview must be a HSL colorscheme string - 'preview' => '194 100% 44%', - 'preview-background' => '194 100% 22%', - // arrays are rgb - 'background' => [0, 86, 113], - 'text' => [255, 255, 255], - // subtitle hex color is BGR (Blue, Green, Red), - 'subtitles' => 'FFFFFF', - // quotes image MUST BE black - 'quotes' => [0, 171, 225], - 'episodeNumberingBg' => [0, 43, 57], - 'episodeNumberingText' => [255, 255, 255], - 'progressbar' => '00ABE1', - 'timestampBg' => '005671', - 'timestampText' => 'FFFFFF', - 'watermarkBg' => '005671', - 'soundwaves' => [214, 245, 255], - ], - 'amber' => [ - // Preview must be a HSL colorscheme string - 'preview' => '17 100% 57%', - 'preview-background' => '17 100% 35%', - // arrays are rgb - 'background' => [177, 50, 0], - 'text' => [255, 255, 255], - // subtitle hex color is BGR (Blue, Green, Red), - 'subtitles' => 'FFFFFF', - // quotes image MUST BE black - 'quotes' => [255, 96, 34], - 'episodeNumberingBg' => [121, 34, 0], - 'episodeNumberingText' => [255, 255, 255], - 'progressbar' => 'FF6022', - 'timestampBg' => 'B13200', - 'timestampText' => 'FFFFFF', - 'watermarkBg' => 'B13200', - 'soundwaves' => [255, 213, 197], - ], - 'jacaranda' => [ - // Preview must be a HSL colorscheme string - 'preview' => '254 72% 52%', - 'preview-background' => '254 73% 30%', - // arrays are rgb - 'background' => [47, 21, 132], - 'text' => [255, 255, 255], - // subtitle hex color is BGR (Blue, Green, Red), - 'subtitles' => 'FFFFFF', - // quotes image MUST BE black - 'quotes' => [86, 45, 221], - 'episodeNumberingBg' => [30, 14, 84], - 'episodeNumberingText' => [255, 255, 255], - 'progressbar' => '562DDD', - 'timestampBg' => '2F1584', - 'timestampText' => 'FFFFFF', - 'watermarkBg' => '2F1584', - 'soundwaves' => [199, 185, 244], - ], - 'onyx' => [ - // Preview must be a HSL colorscheme string - 'preview' => '240 17% 2%', - 'preview-background' => '240 17% 2%', - // arrays are rgb - 'background' => [5, 5, 7], - 'text' => [255, 255, 255], - // subtitle hex color is BGR (Blue, Green, Red), - 'subtitles' => 'FFFFFF', - // quotes image MUST BE black - 'quotes' => [38, 38, 49], - 'episodeNumberingBg' => [0, 0, 0], - 'episodeNumberingText' => [255, 255, 255], - 'progressbar' => 'D5D5E1', - 'timestampBg' => '050507', - 'timestampText' => 'FFFFFF', - 'watermarkBg' => '050507', - 'soundwaves' => [213, 213, 225], - ], - ]; -} diff --git a/app/Libraries/PodcastEpisode.php b/app/Libraries/PodcastEpisode.php index 9b2bf169..cbd260b9 100644 --- a/app/Libraries/PodcastEpisode.php +++ b/app/Libraries/PodcastEpisode.php @@ -10,8 +10,12 @@ declare(strict_types=1); namespace App\Libraries; +use App\Entities\Actor; use App\Entities\Episode; +use CodeIgniter\I18n\Time; use Modules\Fediverse\Core\ObjectType; +use Modules\Media\Entities\Chapters; +use Modules\Media\Entities\Transcript; class PodcastEpisode extends ObjectType { @@ -42,49 +46,49 @@ class PodcastEpisode extends ObjectType $this->id = $episode->link; $this->description = [ - 'type' => 'Note', - 'mediaType' => 'text/markdown', - 'content' => $episode->description_markdown, + 'type' => 'Note', + 'mediaType' => 'text/markdown', + 'content' => $episode->description_markdown, 'contentMap' => [ $episode->podcast->language_code => $episode->description_html, ], ]; $this->image = [ - 'type' => 'Image', + 'type' => 'Image', 'mediaType' => $episode->cover->file_mimetype, - 'url' => $episode->cover->feed_url, + 'url' => $episode->cover->feed_url, ]; // add audio file $this->audio = [ - 'id' => $episode->audio->file_url, - 'type' => 'Audio', - 'name' => esc($episode->title), - 'size' => $episode->audio->file_size, + 'id' => $episode->audio_url, + 'type' => 'Audio', + 'name' => esc($episode->title), + 'size' => $episode->audio->file_size, 'duration' => $episode->audio->duration, - 'url' => [ - 'href' => $episode->audio->file_url, - 'type' => 'Link', + 'url' => [ + 'href' => $episode->audio_url, + 'type' => 'Link', 'mediaType' => $episode->audio->file_mimetype, ], ]; - if ($episode->transcript !== null) { + if ($episode->transcript instanceof Transcript) { $this->audio['transcript'] = $episode->transcript->file_url; } - if ($episode->chapters !== null) { + if ($episode->chapters instanceof Chapters) { $this->audio['chapters'] = $episode->chapters->file_url; } $this->comments = url_to('episode-comments', $episode->podcast->handle, $episode->slug); - if ($episode->published_at !== null) { + if ($episode->published_at instanceof Time) { $this->published = $episode->published_at->format(DATE_W3C); } - if ($episode->podcast->actor !== null) { + if ($episode->podcast->actor instanceof Actor) { $this->attributedTo = $episode->podcast->actor->uri; if ($episode->podcast->actor->followers_url) { diff --git a/app/Libraries/Router.php b/app/Libraries/Router.php index dc760907..5ce2c227 100644 --- a/app/Libraries/Router.php +++ b/app/Libraries/Router.php @@ -14,9 +14,12 @@ declare(strict_types=1); namespace App\Libraries; -use CodeIgniter\Router\Exceptions\RedirectException; +use CodeIgniter\Exceptions\PageNotFoundException; +use CodeIgniter\HTTP\Exceptions\RedirectException; +use CodeIgniter\Router\Exceptions\RouterException; use CodeIgniter\Router\Router as CodeIgniterRouter; -use Config\Services; +use Config\Routing; +use Override; class Router extends CodeIgniterRouter { @@ -28,9 +31,9 @@ class Router extends CodeIgniterRouter * * @return boolean Whether the route was matched or not. */ + #[Override] protected function checkRoutes(string $uri): bool { - /** @noRector RemoveExtraParametersRector */ $routes = $this->collection->getRoutes($this->collection->getHTTPVerb()); // Don't waste any time @@ -41,26 +44,14 @@ class Router extends CodeIgniterRouter $uri = $uri === '/' ? $uri : trim($uri, '/ '); // Loop through the route array looking for wildcards - foreach ($routes as $routeKey => $val) { - // Reset localeSegment - $localeSegment = null; - - $routeKey = $routeKey === '/' ? $routeKey : ltrim($routeKey, '/ '); + foreach ($routes as $routeKey => $handler) { + $routeKey = $routeKey === '/' ? $routeKey : ltrim((string) $routeKey, '/ '); $matchedKey = $routeKey; // Are we dealing with a locale? if (str_contains($routeKey, '{locale}')) { - $localeSegment = array_search( - '{locale}', - preg_split('~[\/]*((^[a-zA-Z0-9])|\(([^()]*)\))*[\/]+~m', $routeKey), - true, - ); - - // Replace it with a regex so it - // will actually match. - $routeKey = str_replace('/', '\/', $routeKey); - $routeKey = str_replace('{locale}', '[^\/]+', $routeKey); + $routeKey = str_replace('{locale}', '[^/]+', $routeKey); } // Does the RegEx match? @@ -69,32 +60,51 @@ class Router extends CodeIgniterRouter // Is this route supposed to redirect to another? if ($this->collection->isRedirect($routeKey)) { + // replacing matched route groups with references: post/([0-9]+) -> post/$1 + $redirectTo = preg_replace_callback('/(\([^\(]+\))/', static function (): string { + static $i = 1; + + return '$' . $i++; + }, (string) (is_array($handler) ? key($handler) : $handler)); + throw new RedirectException( - is_array($val) ? key($val) : $val, + preg_replace('#^' . $routeKey . '$#u', (string) $redirectTo, $uri), $this->collection->getRedirectCode($routeKey), ); } // Store our locale so CodeIgniter object can // assign it to the Request. - if (isset($localeSegment)) { - // The following may be inefficient, but doesn't upset NetBeans :-/ - $temp = explode('/', $uri); - $this->detectedLocale = $temp[$localeSegment]; + if (str_contains($matchedKey, '{locale}')) { + preg_match( + '#^' . str_replace('{locale}', '(?[^/]+)', $matchedKey) . '$#u', + $uri, + $matched, + ); + + if ($this->collection->shouldUseSupportedLocalesOnly() + && ! in_array($matched['locale'], config('App')->supportedLocales, true)) { + // Throw exception to prevent the autorouter, if enabled, + // from trying to find a route + throw PageNotFoundException::forLocaleNotSupported($matched['locale']); + } + + $this->detectedLocale = $matched['locale']; + unset($matched); } // Are we using Closures? If so, then we need // to collect the params into an array // so it can be passed to the controller method later. - if (! is_string($val) && is_callable($val)) { - $this->controller = $val; + if (! is_string($handler) && is_callable($handler)) { + $this->controller = $handler; // Remove the original string from the matches array array_shift($matches); $this->params = $matches; - $this->matchedRoute = [$matchedKey, $val]; + $this->setMatchedRoute($matchedKey, $handler); return true; } @@ -107,8 +117,8 @@ class Router extends CodeIgniterRouter array_key_exists('alternate-content', $this->matchedRouteOptions) && is_array($this->matchedRouteOptions['alternate-content']) ) { - $request = Services::request(); - $negotiate = Services::negotiator(); + $request = service('request'); + $negotiate = service('negotiator'); // Accept header is mandatory if ($request->header('Accept') === null) { @@ -124,62 +134,100 @@ class Router extends CodeIgniterRouter $expectedContentType = $parsedHeader[0]; foreach ($supported as $available) { if ( - $negotiate->callMatch($expectedContentType, $available, true) + ! $negotiate->callMatch($expectedContentType, $available, true) ) { - if ( - array_key_exists( - 'namespace', - $this->matchedRouteOptions[ - 'alternate-content' - ][$available], - ) - ) { - $this->collection->setDefaultNamespace( - $this->matchedRouteOptions[ - 'alternate-content' - ][$available]['namespace'], - ); - } - - $val = - $this->collection->getDefaultNamespace() . - $this->directory . - $this->matchedRouteOptions['alternate-content'][ - $available - ]['controller-method']; - - // no need to continue loop as $val has been overwritten - break; + continue; } + + if ( + array_key_exists( + 'namespace', + $this->matchedRouteOptions[ + 'alternate-content' + ][$available], + ) + ) { + $this->collection->setDefaultNamespace( + $this->matchedRouteOptions[ + 'alternate-content' + ][$available]['namespace'], + ); + } + + $handler = + $this->collection->getDefaultNamespace() . + $this->directory . + $this->matchedRouteOptions['alternate-content'][ + $available + ]['controller-method']; + + // no need to continue loop as $handle has been overwritten + break; } } - // Are we using the default method for back-references? + // Are we using Closures? If so, then we need + // to collect the params into an array + // so it can be passed to the controller method later. + if (! is_string($handler) && is_callable($handler)) { + $this->controller = $handler; - // Support resource route when function with subdirectory - // ex: $routes->resource('Admin/Admins'); - if ( - str_contains($val, '$') && - str_contains($routeKey, '(') && - str_contains($routeKey, '/') - ) { - $replacekey = str_replace('/(.*)', '', $routeKey); - $val = preg_replace('#^' . $routeKey . '$#u', $val, $uri); - $val = str_replace($replacekey, str_replace('/', '\\', $replacekey), $val); - } elseif (str_contains($val, '$') && str_contains($routeKey, '(')) { - $val = preg_replace('#^' . $routeKey . '$#u', $val, $uri); - } elseif (str_contains($val, '/')) { - [$controller, $method] = explode('::', $val); + // Remove the original string from the matches array + array_shift($matches); - // Only replace slashes in the controller, not in the method. - $controller = str_replace('/', '\\', $controller); + $this->params = $matches; - $val = $controller . '::' . $method; + $this->setMatchedRoute($matchedKey, $handler); + + return true; } - $this->setRequest(explode('/', $val)); + if (str_contains((string) $handler, '::')) { + [$controller, $methodAndParams] = explode('::', (string) $handler); + } else { + $controller = $handler; + $methodAndParams = ''; + } - $this->matchedRoute = [$matchedKey, $val]; + // Checks `/` in controller name + if (str_contains((string) $controller, '/')) { + throw RouterException::forInvalidControllerName($handler); + } + + if (str_contains((string) $handler, '$') && str_contains($routeKey, '(')) { + // Checks dynamic controller + if (str_contains((string) $controller, '$')) { + throw RouterException::forDynamicController($handler); + } + + if (config(Routing::class)->multipleSegmentsOneParam === false) { + // Using back-references + $segments = explode( + '/', + (string) preg_replace('#\A' . $routeKey . '\z#u', (string) $handler, $uri), + ); + } else { + if (str_contains($methodAndParams, '/')) { + [$method, $handlerParams] = explode('/', $methodAndParams, 2); + $params = explode('/', $handlerParams); + $handlerSegments = array_merge([$controller . '::' . $method], $params); + } else { + $handlerSegments = [$handler]; + } + + $segments = []; + + foreach ($handlerSegments as $segment) { + $segments[] = $this->replaceBackReferences($segment, $matches); + } + } + } else { + $segments = explode('/', (string) $handler); + } + + $this->setRequest($segments); + + $this->setMatchedRoute($matchedKey, $handler); return true; } diff --git a/app/Libraries/SimpleRSSElement.php b/app/Libraries/RssFeed.php similarity index 54% rename from app/Libraries/SimpleRSSElement.php rename to app/Libraries/RssFeed.php index 77fbb992..631c80f1 100644 --- a/app/Libraries/SimpleRSSElement.php +++ b/app/Libraries/RssFeed.php @@ -11,10 +11,34 @@ declare(strict_types=1); namespace App\Libraries; use DOMDocument; +use Override; use SimpleXMLElement; -class SimpleRSSElement extends SimpleXMLElement +class RssFeed extends SimpleXMLElement { + public const ATOM_NS = 'atom'; + + public const ATOM_NAMESPACE = 'http://www.w3.org/2005/Atom'; + + public const ITUNES_NS = 'itunes'; + + public const ITUNES_NAMESPACE = 'http://www.itunes.com/dtds/podcast-1.0.dtd'; + + public const PODCAST_NS = 'podcast'; + + public const PODCAST_NAMESPACE = 'https://podcastindex.org/namespace/1.0'; + + public function __construct(string $contents = '') + { + parent::__construct(sprintf( + "%s", + $this::ATOM_NAMESPACE, + $this::ITUNES_NAMESPACE, + $this::PODCAST_NAMESPACE, + $contents, + )); + } + /** * Adds a child with $value inside CDATA * @@ -29,7 +53,7 @@ class SimpleRSSElement extends SimpleXMLElement $newChild = parent::addChild($name, null, $namespace); $node = dom_import_simplexml($newChild); $no = $node->ownerDocument; - if ($no !== null) { + if ($no instanceof DOMDocument) { $node->appendChild($no->createCDATASection($value)); } @@ -47,6 +71,7 @@ class SimpleRSSElement extends SimpleXMLElement * * @return static The addChild method returns a SimpleXMLElement object representing the child added to the XML node. */ + #[Override] public function addChild($name, $value = null, $namespace = null, $escape = true): static { $newChild = parent::addChild($name, null, $namespace); @@ -57,12 +82,41 @@ class SimpleRSSElement extends SimpleXMLElement return $newChild; } - if (is_array($value)) { - return $newChild; - } - $node->appendChild($no->createTextNode($value)); return $newChild; } + + /** + * Add RssFeed code into a RssFeed + * + * adapted from: https://stackoverflow.com/a/23527002 + * + * @param self|array $nodes + */ + public function appendNodes(self|array $nodes): void + { + if (! is_array($nodes)) { + $nodes = [$nodes]; + } + + foreach ($nodes as $element) { + $namespaces = $element->getNamespaces(); + $namespace = array_first($namespaces) ?? null; + + if (trim((string) $element) === '') { + $simpleRSS = $this->addChild($element->getName(), null, $namespace); + } else { + $simpleRSS = $this->addChild($element->getName(), (string) $element, $namespace); + } + + foreach ($element->children() as $child) { + $simpleRSS->appendNodes($child); + } + + foreach ($element->attributes() as $name => $value) { + $simpleRSS->addAttribute($name, (string) $value); + } + } + } } diff --git a/app/Libraries/TranscriptParser.php b/app/Libraries/TranscriptParser.php deleted file mode 100644 index c54fec4e..00000000 --- a/app/Libraries/TranscriptParser.php +++ /dev/null @@ -1,110 +0,0 @@ -transcriptContent = $content; - - return $this; - } - - /** - * Adapted from: https://stackoverflow.com/a/11659306 - */ - public function parseSrt(): string | false - { - if (! defined('SRT_STATE_SUBNUMBER')) { - define('SRT_STATE_SUBNUMBER', 0); - } - - if (! defined('SRT_STATE_TIME')) { - define('SRT_STATE_TIME', 1); - } - - if (! defined('SRT_STATE_TEXT')) { - define('SRT_STATE_TEXT', 2); - } - - if (! defined('SRT_STATE_BLANK')) { - define('SRT_STATE_BLANK', 3); - } - - $subs = []; - $state = SRT_STATE_SUBNUMBER; - $subNum = 0; - $subText = ''; - $subTime = ''; - - $lines = explode(PHP_EOL, $this->transcriptContent); - foreach ($lines as $line) { - switch ($state) { - case SRT_STATE_SUBNUMBER: - $subNum = trim($line); - $state = SRT_STATE_TIME; - break; - - case SRT_STATE_TIME: - $subTime = trim($line); - $state = SRT_STATE_TEXT; - break; - - case SRT_STATE_TEXT: - if (trim($line) === '') { - $sub = new stdClass(); - $sub->number = (int) $subNum; - [$startTime, $endTime] = explode(' --> ', $subTime); - $sub->startTime = $this->getSecondsFromTimeString($startTime); - $sub->endTime = $this->getSecondsFromTimeString($endTime); - $sub->text = trim($subText); - $subText = ''; - $state = SRT_STATE_SUBNUMBER; - - $subs[] = $sub; - } else { - if ($subText !== '') { - $subText .= PHP_EOL . $line; - } - - $subText .= $line; - } - - break; - - } - } - - if ($state === SRT_STATE_TEXT) { - // if file was missing the trailing newlines, we'll be in this - // state here. Append the last read text and add the last sub. - // @phpstan-ignore-next-line - $sub->text = $subText; - // @phpstan-ignore-next-line - $subs[] = $sub; - } - - return json_encode($subs, JSON_PRETTY_PRINT); - } - - private function getSecondsFromTimeString(string $timeString): float - { - $timeString = explode(',', $timeString); - return (strtotime($timeString[0]) - strtotime('TODAY')) + (float) "0.{$timeString[1]}"; - } -} diff --git a/app/Libraries/ViewComponents/Component.php b/app/Libraries/ViewComponents/Component.php index 47dc2625..eaf9744a 100644 --- a/app/Libraries/ViewComponents/Component.php +++ b/app/Libraries/ViewComponents/Component.php @@ -4,28 +4,34 @@ declare(strict_types=1); namespace ViewComponents; -class Component implements ComponentInterface -{ - protected string $slot = ''; +use Override; - protected string $class = ''; +abstract class Component implements ComponentInterface +{ + /** + * @var list + */ + protected array $props = []; + + /** + * @var array + */ + protected array $casts = []; + + protected ?string $slot = null; /** * @var array */ - protected array $attributes = [ - 'class' => '', - ]; + protected array $attributes = []; /** * @param array $attributes */ public function __construct(array $attributes) { - helper('viewcomponents'); - // overwrite default attributes if set - $this->attributes = array_merge($this->attributes, $attributes); + $this->attributes = [...$this->attributes, ...$attributes]; if ($attributes !== []) { $this->hydrate($attributes); @@ -42,11 +48,42 @@ class Component implements ComponentInterface if (is_callable([$this, $method])) { $this->{$method}($value); } else { + if (array_key_exists($name, $this->casts)) { + $value = match ($this->casts[$name]) { + 'boolean' => $value === 'true', + 'number' => (int) $value, + 'array' => json_decode(htmlspecialchars_decode($value), true), + default => $value, + }; + } + $this->{$name} = $value; } + + // remove from attributes + if (in_array($name, $this->props, true)) { + unset($this->attributes[$name]); + } + } + + unset($this->attributes['slot']); + } + + public function mergeClass(string $class): void + { + if (! array_key_exists('class', $this->attributes)) { + $this->attributes['class'] = $class; + } else { + $this->attributes['class'] .= ' ' . $class; } } + public function getStringifiedAttributes(): string + { + return stringify_attributes($this->attributes); + } + + #[Override] public function render(): string { return static::class . ': RENDER METHOD NOT IMPLEMENTED'; diff --git a/app/Libraries/ViewComponents/ComponentRenderer.php b/app/Libraries/ViewComponents/ComponentRenderer.php index 09712615..e9a68172 100644 --- a/app/Libraries/ViewComponents/ComponentRenderer.php +++ b/app/Libraries/ViewComponents/ComponentRenderer.php @@ -43,38 +43,38 @@ class ComponentRenderer private function renderSelfClosingTags(string $output): string { // Pattern borrowed and adapted from Laravel's ComponentTagCompiler - // Should match any Component tags + // Should match any Component tags $pattern = "/ < - \s* - (?[A-Z][A-Za-z0-9\.]*?) - \s* + \\s* + x[-\\:](?[\\w\\-\\:\\.]*) + \\s* (? (?: - \s+ + \\s+ (?: (?: - \{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\} + \\{\\{\\s*\\\$attributes(?:[^}]+?)?\\s*\\}\\} ) | (?: - [\w\-:.@]+ + [\\w\\-:.@]+ ( = (?: \\\"[^\\\"]*\\\" | - \'[^\']*\' + \\'[^\\']*\\' | - [^\'\\\"=<>]+ + [^\\'\\\"=<>]+ ) )? ) ) )* - \s* + \\s* ) - \/> + \\/> /x"; /* @@ -82,7 +82,7 @@ class ComponentRenderer $matches[name] = tag name $matches[attributes] = array of attribute string (class="foo") */ - return preg_replace_callback($pattern, function ($match): string { + return preg_replace_callback($pattern, function (array $match): string { $view = $this->locateView($match['name']); $attributes = $this->parseAttributes($match['attributes']); @@ -96,15 +96,16 @@ class ComponentRenderer private function renderPairedTags(string $output): string { - $pattern = '/<\s*(?[A-Z][A-Za-z0-9\.]*?)(?[\s\S\=\'\"]*)>(?.*)<\/\s*\1\s*>/uUsm'; - ini_set('pcre.backtrack_limit', '-1'); + // ini_set('pcre.backtrack_limit', '-1'); + $pattern = '/<\s*x[-\:](?[\w\-\:\.]*?)(?(\s*[\w\-]+\s*=\s*(\'[^\']*\'|\"[^\"]*\"))+\s*)>(?.*)<\/\s*x-\1\s*>/uiUsm'; + /* $matches[0] = full tags matched and all of its content $matches[name] = pascal cased tag name $matches[attributes] = string of tag attributes (class="foo") $matches[slot] = the content inside the tags */ - return preg_replace_callback($pattern, function ($match): string { + return preg_replace_callback($pattern, function (array $match): string { $view = $this->locateView($match['name']); $attributes = $this->parseAttributes($match['attributes']); $attributes['slot'] = $match['slot']; @@ -134,17 +135,17 @@ class ComponentRenderer foreach ($pathsToDiscover as $basePath) { // Look for a class component first - $filePath = $basePath . $this->config->componentsDirectory . '/' . $namePath . '.php'; + $fileKey = $basePath . $this->config->componentsDirectory . '/' . $namePath . '.php'; - if (is_file($filePath)) { - return $filePath; + if (is_file($fileKey)) { + return $fileKey; } $snakeCaseName = strtolower(preg_replace('~(?config->componentsDirectory . '/' . $snakeCaseName . '.php'; + $fileKey = $basePath . $this->config->componentsDirectory . '/' . $snakeCaseName . '.php'; - if (is_file($filePath)) { - return $filePath; + if (is_file($fileKey)) { + return $fileKey; } } @@ -167,8 +168,6 @@ class ComponentRenderer ( \"[^\"]+\" | - \'[^\']+\' - | \\\'[^\\\']+\\\' | [^\s>]+ @@ -204,18 +203,18 @@ class ComponentRenderer { // Locate the class in the same folder as the view $class = $name . '.php'; - $filePath = str_replace($name . '.php', $class, $view); + $fileKey = str_replace($name . '.php', $class, $view); - if ($filePath === '') { + if ($fileKey === '') { return null; } - if (! file_exists($filePath)) { + if (! file_exists($fileKey)) { return null; } $className = service('locator') - ->getClassname($filePath); + ->getClassname($fileKey); if (! class_exists($className)) { return null; diff --git a/app/Libraries/ViewComponents/Config/Services.php b/app/Libraries/ViewComponents/Config/Services.php index bb9c1d21..b7a1c74d 100644 --- a/app/Libraries/ViewComponents/Config/Services.php +++ b/app/Libraries/ViewComponents/Config/Services.php @@ -22,6 +22,7 @@ class Services extends BaseService public static function components(bool $getShared = true): ComponentRenderer { if ($getShared) { + /** @phpstan-ignore return.type */ return self::getSharedInstance('components'); } diff --git a/app/Libraries/ViewComponents/Decorator.php b/app/Libraries/ViewComponents/Decorator.php index 9ca40085..d8e7bfb6 100644 --- a/app/Libraries/ViewComponents/Decorator.php +++ b/app/Libraries/ViewComponents/Decorator.php @@ -5,10 +5,9 @@ declare(strict_types=1); namespace ViewComponents; use CodeIgniter\View\ViewDecoratorInterface; +use Override; /** - * Class Decorator - * * Enables rendering of View Components into the views. * * Borrowed and adapted from https://github.com/lonnieezell/Bonfire2/ @@ -17,6 +16,7 @@ class Decorator implements ViewDecoratorInterface { private static ?ComponentRenderer $components = null; + #[Override] public static function decorate(string $html): string { $components = self::factory(); @@ -29,7 +29,7 @@ class Decorator implements ViewDecoratorInterface */ private static function factory(): ComponentRenderer { - if (self::$components === null) { + if (! self::$components instanceof ComponentRenderer) { self::$components = new ComponentRenderer(); } diff --git a/app/Libraries/ViewComponents/Helpers/viewcomponents_helper.php b/app/Libraries/ViewComponents/Helpers/viewcomponents_helper.php deleted file mode 100644 index 9c7caf7d..00000000 --- a/app/Libraries/ViewComponents/Helpers/viewcomponents_helper.php +++ /dev/null @@ -1,33 +0,0 @@ - $val) { - $atts .= ($js) ? $key . '=' . esc($val, 'js') . ',' : ' ' . $key . '="' . $val . '"'; - } - - return rtrim($atts, ','); - } -} diff --git a/app/Libraries/ViewThemes/Config/Themes.php b/app/Libraries/ViewThemes/Config/Themes.php index c22ac27c..61ab4c9f 100644 --- a/app/Libraries/ViewThemes/Config/Themes.php +++ b/app/Libraries/ViewThemes/Config/Themes.php @@ -16,9 +16,9 @@ class Themes extends BaseConfig * @var array */ public array $themes = [ - 'app' => 'cp_app', - 'admin' => 'cp_admin', + 'app' => 'cp_app', + 'admin' => 'cp_admin', 'install' => 'cp_install', - 'auth' => 'cp_auth', + 'auth' => 'cp_auth', ]; } diff --git a/app/Libraries/ViewThemes/Theme.php b/app/Libraries/ViewThemes/Theme.php index 30020dcb..b1b9b008 100644 --- a/app/Libraries/ViewThemes/Theme.php +++ b/app/Libraries/ViewThemes/Theme.php @@ -19,7 +19,7 @@ class Theme protected static $defaultTheme = 'app'; /** - * @var string + * @var ?string */ protected static $currentTheme; @@ -46,7 +46,7 @@ class Theme /** * Returns the path to the specified theme folder. If no theme is provided, will use the current theme. */ - public static function path(string $theme = null): string + public static function path(?string $theme = null): string { if ($theme === null) { $theme = static::current(); @@ -71,9 +71,7 @@ class Theme */ public static function current(): string { - return static::$currentTheme !== null - ? static::$currentTheme - : static::$defaultTheme; + return static::$currentTheme ?? static::$defaultTheme; } /** diff --git a/app/Libraries/Vite/Config/Vite.php b/app/Libraries/Vite/Config/Vite.php deleted file mode 100644 index 88471e78..00000000 --- a/app/Libraries/Vite/Config/Vite.php +++ /dev/null @@ -1,20 +0,0 @@ -|null - */ - protected ?array $manifestData = null; - - /** - * @var array|null - */ - protected ?array $manifestCSSData = null; - - public function asset(string $path, string $type): string - { - if (config('Vite')->environment !== 'production') { - return $this->loadDev($path, $type); - } - - return $this->loadProd($path, $type); - } - - private function loadDev(string $path, string $type): string - { - return $this->getHtmlTag(config('Vite')->baseUrl . config('Vite')->assetsRoot . "/{$path}", $type); - } - - private function loadProd(string $path, string $type): string - { - if ($type === 'css') { - if ($this->manifestCSSData === null) { - $cacheName = 'vite-manifest-css'; - if (! ($cachedManifestCSS = cache($cacheName))) { - $manifestCSSPath = config('Vite') - ->assetsRoot . '/' . config('Vite') - ->manifestCSSFile; - try { - if (($manifestCSSContent = file_get_contents($manifestCSSPath)) !== false) { - $cachedManifestCSS = json_decode($manifestCSSContent, true); - cache() - ->save($cacheName, $cachedManifestCSS, DECADE); - } - } catch (ErrorException) { - // ERROR when getting the manifest-css file - die("Could not load css manifest: {$manifestCSSPath} file not found!"); - } - } - - $this->manifestCSSData = $cachedManifestCSS; - } - - if (array_key_exists($path, $this->manifestCSSData)) { - return $this->getHtmlTag( - '/' . config('Vite')->assetsRoot . '/' . $this->manifestCSSData[$path]['file'], - 'css' - ); - } - } - - if ($this->manifestData === null) { - $cacheName = 'vite-manifest'; - if (! ($cachedManifest = cache($cacheName))) { - $manifestPath = config('Vite') - ->assetsRoot . '/' . config('Vite') - ->manifestFile; - try { - if (($manifestContents = file_get_contents($manifestPath)) !== false) { - $cachedManifest = json_decode($manifestContents, true); - cache() - ->save($cacheName, $cachedManifest, DECADE); - } - } catch (ErrorException) { - // ERROR when retrieving the manifest file - die("Could not load manifest: {$manifestPath} file not found!"); - } - } - - $this->manifestData = $cachedManifest; - } - - $html = ''; - if (array_key_exists($path, $this->manifestData)) { - $manifestElement = $this->manifestData[$path]; - - // import css dependencies if any - if (array_key_exists('css', $manifestElement)) { - foreach ($manifestElement['css'] as $cssFile) { - $html .= $this->getHtmlTag('/' . config('Vite')->assetsRoot . '/' . $cssFile, 'css'); - } - } - - // import dependencies first for faster js loading - if (array_key_exists('imports', $manifestElement)) { - foreach ($manifestElement['imports'] as $importPath) { - if (array_key_exists($importPath, $this->manifestData)) { - $html .= $this->getHtmlTag( - '/' . config('Vite')->assetsRoot . '/' . $this->manifestData[$importPath]['file'], - 'js' - ); - } - } - } - - $html .= $this->getHtmlTag('/' . config('Vite')->assetsRoot . '/' . $manifestElement['file'], $type); - } - - return $html; - } - - private function getHtmlTag(string $assetUrl, string $type): string - { - return match ($type) { - 'css' => << - CODE_SAMPLE -, - 'js' => << - CODE_SAMPLE -, - default => '', - }; - } -} diff --git a/app/Models/ActorModel.php b/app/Models/ActorModel.php index 47b35485..59369100 100644 --- a/app/Models/ActorModel.php +++ b/app/Models/ActorModel.php @@ -12,25 +12,18 @@ namespace App\Models; use App\Entities\Actor; use Modules\Fediverse\Models\ActorModel as FediverseActorModel; +use Override; class ActorModel extends FediverseActorModel { /** - * @var string + * @var class-string */ protected $returnType = Actor::class; + #[Override] public function getActorById(int $id): ?Actor { - $cacheName = config('Fediverse') - ->cachePrefix . "actor#{$id}"; - if (! ($found = cache($cacheName))) { - $found = $this->find($id); - - cache() - ->save($cacheName, $found, DECADE); - } - - return $found; + return $this->find($id); } } diff --git a/app/Models/CategoryModel.php b/app/Models/CategoryModel.php index a3858e2a..2c67efeb 100644 --- a/app/Models/CategoryModel.php +++ b/app/Models/CategoryModel.php @@ -26,12 +26,12 @@ class CategoryModel extends Model protected $primaryKey = 'id'; /** - * @var string[] + * @var list */ protected $allowedFields = ['parent_id', 'code', 'apple_category', 'google_category']; /** - * @var string + * @var class-string */ protected $returnType = Category::class; @@ -65,17 +65,17 @@ class CategoryModel extends Model $options = array_reduce( $categories, static function (array $result, Category $category): array { - $result[$category->id] = ''; - if ($category->parent !== null) { - $result[$category->id] = lang( - 'Podcast.category_options.' . $category->parent->code, - [], - null, - false - ) . ' › '; + $label = ''; + if ($category->parent instanceof Category) { + $label = lang('Podcast.category_options.' . $category->parent->code) . ' › '; } - $result[$category->id] .= lang('Podcast.category_options.' . $category->code, [], null, false); + $label .= lang('Podcast.category_options.' . $category->code); + + $result[] = [ + 'value' => $category->id, + 'label' => $label, + ]; return $result; }, [], @@ -116,7 +116,7 @@ class CategoryModel extends Model $categoriesIds, static function (array $result, int $categoryId) use ($podcastId): array { $result[] = [ - 'podcast_id' => $podcastId, + 'podcast_id' => $podcastId, 'category_id' => $categoryId, ]; return $result; diff --git a/app/Models/ClipModel.php b/app/Models/ClipModel.php index d4e01dcf..cc5209c9 100644 --- a/app/Models/ClipModel.php +++ b/app/Models/ClipModel.php @@ -33,7 +33,7 @@ class ClipModel extends Model protected $primaryKey = 'id'; /** - * @var string[] + * @var list */ protected $allowedFields = [ 'id', @@ -53,9 +53,6 @@ class ClipModel extends Model 'job_ended_at', ]; - /** - * @noRector - */ protected $returnType = BaseClip::class; /** @@ -70,8 +67,8 @@ class ClipModel extends Model public function __construct( protected string $type = 'audio', - ConnectionInterface &$db = null, - ValidationInterface $validation = null + ?ConnectionInterface &$db = null, + ?ValidationInterface $validation = null, ) { switch ($type) { case 'audio': @@ -94,11 +91,10 @@ class ClipModel extends Model if (! ($found = cache($cacheName))) { $clip = $this->find($videoClipId); - if ($clip === null) { + if (! $clip instanceof BaseClip) { return null; } - // @phpstan-ignore-next-line $found = new VideoClip($clip->toArray()); cache() @@ -116,7 +112,7 @@ class ClipModel extends Model public function getScheduledVideoClips(): array { $found = $this->where([ - 'type' => 'video', + 'type' => 'video', 'status' => 'queued', ]) ->orderBy('created_at') @@ -134,7 +130,7 @@ class ClipModel extends Model $result = $this->builder() ->select('COUNT(*) as `running_count`') ->where([ - 'type' => 'video', + 'type' => 'video', 'status' => 'running', ]) ->get() @@ -151,7 +147,7 @@ class ClipModel extends Model 'podcast_id' => $videoClip->podcast_id, 'episode_id' => $videoClip->episode_id, 'start_time' => $videoClip->start_time, - 'duration' => $videoClip->duration, + 'duration' => $videoClip->duration, ]) ->where('JSON_EXTRACT(`metadata`, "$.format")', $videoClip->format) ->where('JSON_EXTRACT(`metadata`, "$.theme.name")', $videoClip->theme['name']) @@ -165,15 +161,21 @@ class ClipModel extends Model return (int) $result[0]['id']; } - public function deleteVideoClip(int $podcastId, int $episodeId, int $clipId): BaseResult | bool + public function deleteVideoClip(int $clipId): BaseResult | bool { $this->clearVideoClipCache($clipId); - return $this->delete([ - 'podcast_id' => $podcastId, - 'episode_id' => $episodeId, - 'id' => $clipId, - ]); + return $this->delete($clipId); + } + + public function getClipCount(int $podcastId, int $episodeId): int + { + return $this + ->where([ + 'podcast_id' => $podcastId, + 'episode_id' => $episodeId, + ]) + ->countAllResults(); } public function clearVideoClipCache(int $clipId): void @@ -188,11 +190,10 @@ class ClipModel extends Model if (! ($found = cache($cacheName))) { $clip = $this->find($soundbiteId); - if ($clip === null) { + if (! $clip instanceof BaseClip) { return null; } - // @phpstan-ignore-next-line $found = new Soundbite($clip->toArray()); cache() @@ -214,7 +215,7 @@ class ClipModel extends Model $found = $this->where([ 'episode_id' => $episodeId, 'podcast_id' => $podcastId, - 'type' => 'audio', + 'type' => 'audio', ]) ->orderBy('start_time') ->findAll(); @@ -234,11 +235,7 @@ class ClipModel extends Model { $this->clearSoundbiteCache($podcastId, $episodeId, $clipId); - return $this->delete([ - 'podcast_id' => $podcastId, - 'episode_id' => $episodeId, - 'id' => $clipId, - ]); + return $this->delete($clipId); } public function clearSoundbiteCache(int $podcastId, int $episodeId, int $clipId): void diff --git a/app/Models/CreditModel.php b/app/Models/CreditModel.php index 021b81d6..23342d4c 100644 --- a/app/Models/CreditModel.php +++ b/app/Models/CreditModel.php @@ -21,7 +21,7 @@ class CreditModel extends Model protected $table = 'credits'; /** - * @var string + * @var class-string */ protected $returnType = Credit::class; } diff --git a/app/Models/EpisodeCommentModel.php b/app/Models/EpisodeCommentModel.php index af096172..60582586 100644 --- a/app/Models/EpisodeCommentModel.php +++ b/app/Models/EpisodeCommentModel.php @@ -10,6 +10,8 @@ declare(strict_types=1); namespace App\Models; +use App\Entities\Actor; +use App\Entities\Episode; use App\Entities\EpisodeComment; use App\Libraries\CommentObject; use CodeIgniter\Database\BaseBuilder; @@ -18,13 +20,12 @@ use CodeIgniter\I18n\Time; use Michalsn\Uuid\UuidModel; use Modules\Fediverse\Activities\CreateActivity; use Modules\Fediverse\Activities\DeleteActivity; -use Modules\Fediverse\Models\ActivityModel; use Modules\Fediverse\Objects\TombstoneObject; class EpisodeCommentModel extends UuidModel { /** - * @var string + * @var class-string */ protected $returnType = EpisodeComment::class; @@ -39,7 +40,7 @@ class EpisodeCommentModel extends UuidModel protected $uuidFields = ['id', 'in_reply_to_id']; /** - * @var string[] + * @var list */ protected $allowedFields = [ 'id', @@ -56,7 +57,7 @@ class EpisodeCommentModel extends UuidModel ]; /** - * @var string[] + * @var list */ protected $beforeInsert = ['setCommentId']; @@ -73,7 +74,7 @@ class EpisodeCommentModel extends UuidModel return $found; } - public function addComment(EpisodeComment $comment, bool $registerActivity = true): string | false + public function addComment(EpisodeComment $comment, bool $registerActivity = true): bool|int|object|string { $this->db->transStart(); @@ -85,11 +86,13 @@ class EpisodeCommentModel extends UuidModel } if ($comment->in_reply_to_id === null) { - (new EpisodeModel())->builder() + new EpisodeModel() + ->builder() ->where('id', $comment->episode_id) ->increment('comments_count'); } else { - (new self())->builder() + new self() + ->builder() ->where('id', service('uuid')->fromString($comment->in_reply_to_id)->getBytes()) ->increment('replies_count'); } @@ -101,7 +104,7 @@ class EpisodeCommentModel extends UuidModel 'episode-comment', esc($comment->actor->username), $comment->episode->slug, - $comment->id + $comment->id, ); $createActivity = new CreateActivity(); @@ -109,7 +112,7 @@ class EpisodeCommentModel extends UuidModel ->set('actor', $comment->actor->uri) ->set('object', new CommentObject($comment)); - $activityId = model(ActivityModel::class, false) + $activityId = model('ActivityModel', false) ->newActivity( 'Create', $comment->actor_id, @@ -122,7 +125,7 @@ class EpisodeCommentModel extends UuidModel $createActivity->set('id', url_to('activity', esc($comment->actor->username), $activityId)); - model(ActivityModel::class, false) + model('ActivityModel', false) ->update($activityId, [ 'payload' => $createActivity->toJSON(), ]); @@ -152,7 +155,7 @@ class EpisodeCommentModel extends UuidModel ->set('actor', $comment->actor->uri) ->set('object', $tombstoneObject); - $activityId = model(ActivityModel::class, false) + $activityId = model('ActivityModel', false) ->newActivity( 'Delete', $comment->actor_id, @@ -165,7 +168,7 @@ class EpisodeCommentModel extends UuidModel $deleteActivity->set('id', url_to('activity', esc($comment->actor->username), $activityId)); - model(ActivityModel::class, false) + model('ActivityModel', false) ->update($activityId, [ 'payload' => $deleteActivity->toJSON(), ]); @@ -175,11 +178,12 @@ class EpisodeCommentModel extends UuidModel ->delete($comment->id); if ($comment->in_reply_to_id === null) { - model(EpisodeModel::class, false)->builder() + model('EpisodeModel', false)->builder() ->where('id', $comment->episode_id) ->decrement('comments_count'); } else { - (new self())->builder() + new self() + ->builder() ->where('id', service('uuid')->fromString($comment->in_reply_to_id)->getBytes()) ->decrement('replies_count'); } @@ -195,16 +199,14 @@ class EpisodeCommentModel extends UuidModel * Retrieves all published posts for a given episode ordered by publication date * * @return EpisodeComment[] - * - * @noRector ReturnTypeDeclarationRector */ public function getEpisodeComments(int $episodeId): array { // TODO: merge with replies from posts linked to episode linked $episodeCommentsBuilder = $this->builder(); - $episodeComments = $episodeCommentsBuilder->select('*, 0 as is_from_post') + $episodeComments = $episodeCommentsBuilder->select('*, 0 as is_private, 0 as is_from_post') ->where([ - 'episode_id' => $episodeId, + 'episode_id' => $episodeId, 'in_reply_to_id' => null, ]) ->getCompiledSelect(); @@ -212,29 +214,33 @@ class EpisodeCommentModel extends UuidModel $postModel = new PostModel(); $episodePostsRepliesBuilder = $postModel->builder(); $episodePostsReplies = $episodePostsRepliesBuilder->select( - 'id, uri, episode_id, actor_id, in_reply_to_id, message, message_html, favourites_count as likes_count, replies_count, published_at as created_at, created_by, 1 as is_from_post' + 'id, uri, episode_id, actor_id, in_reply_to_id, message, message_html, favourites_count as likes_count, replies_count, published_at as created_at, created_by, is_private, 1 as is_from_post', ) ->whereIn('in_reply_to_id', static function (BaseBuilder $builder) use (&$episodeId): BaseBuilder { return $builder->select('id') - ->from(config('Fediverse')->tablesPrefix . 'posts') + ->from('fediverse_posts') ->where([ - 'episode_id' => $episodeId, + 'episode_id' => $episodeId, 'in_reply_to_id' => null, ]); }) - ->where('`created_at` <= UTC_TIMESTAMP()', null, false) - ->getCompiledSelect(); + ->where('`created_at` <= UTC_TIMESTAMP()', null, false); + + // do not get private replies if public + if (! can_user_interact()) { + $episodePostsRepliesBuilder->where('is_private', false); + } + + $episodePostsReplies = $episodePostsRepliesBuilder->getCompiledSelect(); /** @var BaseResult $allEpisodeComments */ $allEpisodeComments = $this->db->query( - $episodeComments . ' UNION ' . $episodePostsReplies . ' ORDER BY created_at ASC' + $episodeComments . ' UNION ' . $episodePostsReplies . ' ORDER BY created_at ASC', ); - // FIXME:? - // @phpstan-ignore-next-line return $this->convertUuidFieldsToStrings( $allEpisodeComments->getCustomResultObject($this->tempReturnType), - $this->tempReturnType + $this->tempReturnType, ); } @@ -294,12 +300,20 @@ class EpisodeCommentModel extends UuidModel $data['data']['id'] = $uuid4->toString(); if (! isset($data['data']['uri'])) { - $actor = model(ActorModel::class, false) + $actor = model('ActorModel', false) ->getActorById((int) $data['data']['actor_id']); - $episode = model(EpisodeModel::class, false) + $episode = model('EpisodeModel', false) ->find((int) $data['data']['episode_id']); - $data['data']['uri'] = url_to('episode-comment', esc($actor->username), $episode->slug, $uuid4->toString()); + if (! $episode instanceof Episode) { + return $data; + } + + if (! $actor instanceof Actor) { + return $data; + } + + $data['data']['uri'] = url_to('episode-comment', $actor->username, $episode->slug, $uuid4->toString()); } return $data; diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php index dcf40ba5..c1ec3a02 100644 --- a/app/Models/EpisodeModel.php +++ b/app/Models/EpisodeModel.php @@ -11,11 +11,13 @@ declare(strict_types=1); namespace App\Models; use App\Entities\Episode; +use CodeIgniter\Database\BaseBuilder; use CodeIgniter\Database\BaseResult; use CodeIgniter\I18n\Time; -use CodeIgniter\Model; +use Michalsn\Uuid\UuidModel; +use Ramsey\Uuid\Lazy\LazyUuidFromString; -class EpisodeModel extends Model +class EpisodeModel extends UuidModel { /** * TODO: remove, shouldn't be here @@ -24,44 +26,48 @@ class EpisodeModel extends Model */ public static $themes = [ 'light-transparent' => [ - 'style' => - 'background-color: #fff; background-image: linear-gradient(45deg, #ccc 12.5%, transparent 12.5%, transparent 50%, #ccc 50%, #ccc 62.5%, transparent 62.5%, transparent 100%); background-size: 5.66px 5.66px;', + 'style' => 'background-color: #fff; background-image: linear-gradient(45deg, #ccc 12.5%, transparent 12.5%, transparent 50%, #ccc 50%, #ccc 62.5%, transparent 62.5%, transparent 100%); background-size: 5.66px 5.66px;', 'background' => 'transparent', - 'text' => '#000', - 'inverted' => '#fff', + 'text' => '#000', + 'inverted' => '#fff', ], 'light' => [ - 'style' => 'background-color: #fff;', + 'style' => 'background-color: #fff;', 'background' => '#fff', - 'text' => '#000', - 'inverted' => '#fff', + 'text' => '#000', + 'inverted' => '#fff', ], 'dark-transparent' => [ - 'style' => - 'background-color: #001f1a; background-image: linear-gradient(45deg, #888 12.5%, transparent 12.5%, transparent 50%, #888 50%, #888 62.5%, transparent 62.5%, transparent 100%); background-size: 5.66px 5.66px;', + 'style' => 'background-color: #001f1a; background-image: linear-gradient(45deg, #888 12.5%, transparent 12.5%, transparent 50%, #888 50%, #888 62.5%, transparent 62.5%, transparent 100%); background-size: 5.66px 5.66px;', 'background' => 'transparent', - 'text' => '#fff', - 'inverted' => '#000', + 'text' => '#fff', + 'inverted' => '#000', ], 'dark' => [ - 'style' => 'background-color: #001f1a;', + 'style' => 'background-color: #001f1a;', 'background' => '#313131', - 'text' => '#fff', - 'inverted' => '#000', + 'text' => '#fff', + 'inverted' => '#000', ], ]; + /** + * @var string[] + */ + protected $uuidFields = ['preview_id']; + /** * @var string */ protected $table = 'episodes'; /** - * @var string[] + * @var list */ protected $allowedFields = [ 'id', 'podcast_id', + 'preview_id', 'guid', 'title', 'slug', @@ -81,8 +87,8 @@ class EpisodeModel extends Model 'location_name', 'location_geo', 'location_osm', - 'custom_rss', 'is_published_on_hubs', + 'downloads_count', 'posts_count', 'comments_count', 'is_premium', @@ -92,7 +98,7 @@ class EpisodeModel extends Model ]; /** - * @var string + * @var class-string */ protected $returnType = Episode::class; @@ -105,33 +111,33 @@ class EpisodeModel extends Model * @var array */ protected $validationRules = [ - 'podcast_id' => 'required', - 'title' => 'required', - 'slug' => 'required|regex_match[/^[a-zA-Z0-9\-]{1,128}$/]', - 'audio_id' => 'required', - 'description_markdown' => 'required', - 'number' => 'is_natural_no_zero|permit_empty', - 'season_number' => 'is_natural_no_zero|permit_empty', - 'type' => 'required', + 'podcast_id' => 'required', + 'title' => 'required', + 'slug' => 'required|regex_match[/^[a-zA-Z0-9\-]{1,128}$/]', + 'audio_id' => 'required', + 'description_markdown' => 'required', + 'number' => 'is_natural_no_zero|permit_empty', + 'season_number' => 'is_natural_no_zero|permit_empty', + 'type' => 'required', 'transcript_remote_url' => 'valid_url_strict|permit_empty', - 'chapters_remote_url' => 'valid_url_strict|permit_empty', - 'published_at' => 'valid_date|permit_empty', - 'created_by' => 'required', - 'updated_by' => 'required', + 'chapters_remote_url' => 'valid_url_strict|permit_empty', + 'published_at' => 'valid_date|permit_empty', + 'created_by' => 'required', + 'updated_by' => 'required', ]; /** - * @var string[] + * @var list */ protected $afterInsert = ['writeEnclosureMetadata', 'clearCache']; /** - * @var string[] + * @var list */ protected $afterUpdate = ['clearCache', 'writeEnclosureMetadata']; /** - * @var string[] + * @var list */ protected $beforeDelete = ['clearCache']; @@ -189,6 +195,38 @@ class EpisodeModel extends Model return $found; } + public function getEpisodeByPreviewId(string $previewId): ?Episode + { + $cacheName = "podcast_episode-preview#{$previewId}"; + if (! ($found = cache($cacheName))) { + $builder = $this->where([ + 'preview_id' => $this->uuid->fromString($previewId) + ->getBytes(), + ]); + + $found = $builder->first(); + + cache() + ->save($cacheName, $found, DECADE); + } + + return $found; + } + + public function setEpisodePreviewId(int $episodeId): string|false + { + /** @var LazyUuidFromString $uuid */ + $uuid = $this->uuid->{$this->uuidVersion}(); + + if (! $this->update($episodeId, [ + 'preview_id' => $uuid, + ])) { + return false; + } + + return (string) $uuid; + } + /** * Gets all episodes for a podcast ordered according to podcast type Filtered depending on year or season * @@ -197,8 +235,8 @@ class EpisodeModel extends Model public function getPodcastEpisodes( int $podcastId, string $podcastType, - string $year = null, - string $season = null + ?string $year = null, + ?string $season = null, ): array { $cacheName = implode( '_', @@ -234,13 +272,7 @@ class EpisodeModel extends Model $secondsToNextUnpublishedEpisode = $this->getSecondsToNextUnpublishedEpisode($podcastId); cache() - ->save( - $cacheName, - $found, - $secondsToNextUnpublishedEpisode - ? $secondsToNextUnpublishedEpisode - : DECADE, - ); + ->save($cacheName, $found, $secondsToNextUnpublishedEpisode ?: DECADE); } return $found; @@ -284,11 +316,9 @@ class EpisodeModel extends Model public function getCurrentSeasonNumber(int $podcastId): ?int { $result = $this->builder() - ->select('MAX(season_number) as current_season_number') - ->where([ - 'podcast_id' => $podcastId, - 'published_at IS NOT' => null, - ]) + ->selectMax('season_number', 'current_season_number') + ->where('podcast_id', $podcastId) + ->where('`published_at` <= UTC_TIMESTAMP()', null, false) ->get() ->getResultArray(); @@ -298,12 +328,13 @@ class EpisodeModel extends Model public function getNextEpisodeNumber(int $podcastId, ?int $seasonNumber): int { $result = $this->builder() - ->select('MAX(number) as next_episode_number') + ->selectMax('number', 'next_episode_number') ->where([ - 'podcast_id' => $podcastId, + 'podcast_id' => $podcastId, 'season_number' => $seasonNumber, - 'published_at IS NOT' => null, - ])->get() + ]) + ->where('`published_at` <= UTC_TIMESTAMP()', null, false) + ->get() ->getResultArray(); return (int) $result[0]['next_episode_number'] + 1; @@ -316,16 +347,15 @@ class EpisodeModel extends Model { $result = $this->builder() ->select( - 'COUNT(DISTINCT season_number) as number_of_seasons, COUNT(*) as number_of_episodes, MIN(published_at) as first_published_at' + 'COUNT(DISTINCT season_number) as number_of_seasons, COUNT(*) as number_of_episodes, MIN(published_at) as first_published_at', ) - ->where([ - 'podcast_id' => $podcastId, - 'published_at IS NOT' => null, - ])->get() + ->where('podcast_id', $podcastId) + ->where('`published_at` <= UTC_TIMESTAMP()', null, false) + ->get() ->getResultArray(); $stats = [ - 'number_of_seasons' => (int) $result[0]['number_of_seasons'], + 'number_of_seasons' => (int) $result[0]['number_of_seasons'], 'number_of_episodes' => (int) $result[0]['number_of_episodes'], ]; @@ -338,30 +368,32 @@ class EpisodeModel extends Model public function resetCommentsCount(): int | false { - $episodeCommentsCount = (new EpisodeCommentModel())->builder() + $episodeCommentsCount = new EpisodeCommentModel() + ->builder() ->select('episode_id, COUNT(*) as `comments_count`') - ->where('in_reply_to_id', null) + ->where('in_reply_to_id') ->groupBy('episode_id') ->getCompiledSelect(); - $postsTable = config('Fediverse') - ->tablesPrefix . 'posts'; - $episodePostsRepliesCount = (new PostModel())->builder() - ->select($postsTable . '.episode_id as episode_id, COUNT(*) as `comments_count`') - ->join($postsTable . ' as fp', $postsTable . '.id = fp.in_reply_to_id') - ->where($postsTable . '.in_reply_to_id', null) - ->groupBy($postsTable . '.episode_id') + $episodePostsRepliesCount = new PostModel() + ->builder() + ->select('fediverse_posts.episode_id as episode_id, COUNT(*) as `comments_count`') + ->join('fediverse_posts as fp', 'fediverse_posts.id = fp.in_reply_to_id') + ->where('fediverse_posts.in_reply_to_id') + ->where('fediverse_posts.episode_id IS NOT') + ->groupBy('fediverse_posts.episode_id') ->getCompiledSelect(); /** @var BaseResult $query */ $query = $this->db->query( - 'SELECT `episode_id` as `id`, SUM(`comments_count`) as `comments_count` FROM (' . $episodeCommentsCount . ' UNION ALL ' . $episodePostsRepliesCount . ') x GROUP BY `episode_id`' + 'SELECT `episode_id` as `id`, SUM(`comments_count`) as `comments_count` FROM (' . $episodeCommentsCount . ' UNION ALL ' . $episodePostsRepliesCount . ') x GROUP BY `episode_id`', ); $countsPerEpisodeId = $query->getResultArray(); if ($countsPerEpisodeId !== []) { - return (new self())->updateBatch($countsPerEpisodeId, 'id'); + return new self() + ->updateBatch($countsPerEpisodeId, 'id'); } return 0; @@ -371,12 +403,8 @@ class EpisodeModel extends Model { $episodePostsCount = $this->builder() ->select('episodes.id, COUNT(*) as `posts_count`') - ->join( - config('Fediverse') - ->tablesPrefix . 'posts', - 'episodes.id = ' . config('Fediverse')->tablesPrefix . 'posts.episode_id' - ) - ->where('in_reply_to_id', null) + ->join('fediverse_posts', 'episodes.id = fediverse_posts.episode_id') + ->where('in_reply_to_id') ->groupBy('episodes.id') ->get() ->getResultArray(); @@ -395,7 +423,21 @@ class EpisodeModel extends Model */ public function clearCache(array $data): array { - $episode = (new self())->find(is_array($data['id']) ? $data['id'][0] : $data['id']); + /** @var int|null $episodeId */ + $episodeId = is_array($data['id']) ? $data['id'][0] : $data['id']; + + if ($episodeId === null) { + // Multiple episodes have been updated, do nothing + return $data; + } + + /** @var ?Episode $episode */ + $episode = new self() + ->find($episodeId); + + if (! $episode instanceof Episode) { + return $data; + } // delete podcast cache cache() @@ -403,7 +445,7 @@ class EpisodeModel extends Model cache() ->deleteMatching("podcast-{$episode->podcast->handle}*"); cache() - ->delete("podcast_episode#{$episode->id}"); + ->deleteMatching('podcast_episode*'); cache() ->deleteMatching("page_podcast#{$episode->podcast_id}*"); cache() @@ -420,7 +462,53 @@ class EpisodeModel extends Model ->where([ 'podcast_id' => $podcastId, 'is_premium' => true, - ])->countAllResults() > 0; + ]) + ->where('`published_at` <= UTC_TIMESTAMP()', null, false) + ->countAllResults() > 0; + } + + public function fullTextSearch(string $query): ?BaseBuilder + { + $prefix = $this->db->getPrefix(); + $episodeTable = $prefix . $this->builder()->getTable(); + + $podcastModel = (new PodcastModel()); + + $podcastTable = $podcastModel->db->getPrefix() . $podcastModel->builder()->getTable(); + + $this->builder() + ->select('' . $episodeTable . '.*') + ->select(' + ' . $this->getFullTextMatchClauseForEpisodes($episodeTable, $query) . ' as episodes_score, + ' . $podcastModel->getFullTextMatchClauseForPodcasts($podcastTable, $query) . ' as podcasts_score, + ') + ->select("{$podcastTable}.created_at AS podcast_created_at") + ->select( + "{$podcastTable}.title as podcast_title, {$podcastTable}.handle as podcast_handle, {$podcastTable}.description_markdown as podcast_description_markdown", + ) + ->join($podcastTable, "{$podcastTable} on {$podcastTable}.id = {$episodeTable}.podcast_id") + ->where(' + (' . + $this->getFullTextMatchClauseForEpisodes($episodeTable, $query) + . 'OR' . + $podcastModel->getFullTextMatchClauseForPodcasts($podcastTable, $query) + . ') + ', ); + + return $this->builder; + } + + public function getFullTextMatchClauseForEpisodes(string $table, string $value): string + { + return ' + MATCH ( + ' . $table . '.title, + ' . $table . '.description_markdown, + ' . $table . '.slug, + ' . $table . '.location_name + ) + AGAINST(' . $this->db->escape($value) . ') + '; } /** @@ -430,9 +518,23 @@ class EpisodeModel extends Model */ protected function writeEnclosureMetadata(array $data): array { - helper('id3'); + /** @var int|null $episodeId */ + $episodeId = is_array($data['id']) ? $data['id'][0] : $data['id']; - $episode = (new self())->find(is_array($data['id']) ? $data['id'][0] : $data['id']); + if ($episodeId === null) { + // Multiple episodes have been updated, do nothing + return $data; + } + + /** @var ?Episode $episode */ + $episode = new self() + ->find($episodeId); + + if (! $episode instanceof Episode) { + return $data; + } + + helper('id3'); write_audio_file_tags($episode); diff --git a/app/Models/LanguageModel.php b/app/Models/LanguageModel.php index 20380200..b68a9197 100644 --- a/app/Models/LanguageModel.php +++ b/app/Models/LanguageModel.php @@ -26,12 +26,12 @@ class LanguageModel extends Model protected $primaryKey = 'id'; /** - * @var string[] + * @var list */ protected $allowedFields = ['code', 'native_name']; /** - * @var string + * @var class-string */ protected $returnType = Language::class; @@ -56,7 +56,10 @@ class LanguageModel extends Model $options = array_reduce( $languages, static function (array $result, Language $language): array { - $result[$language->code] = $language->native_name; + $result[] = [ + 'value' => $language->code, + 'label' => $language->native_name, + ]; return $result; }, [], diff --git a/app/Models/LikeModel.php b/app/Models/LikeModel.php index f4eebe36..f3f382de 100644 --- a/app/Models/LikeModel.php +++ b/app/Models/LikeModel.php @@ -15,8 +15,8 @@ use App\Entities\Like; use Michalsn\Uuid\UuidModel; use Modules\Fediverse\Activities\LikeActivity; use Modules\Fediverse\Activities\UndoActivity; +use Modules\Fediverse\Entities\Activity; use Modules\Fediverse\Entities\Actor; -use Modules\Fediverse\Models\ActivityModel; class LikeModel extends UuidModel { @@ -31,12 +31,12 @@ class LikeModel extends UuidModel protected $uuidFields = ['comment_id']; /** - * @var string[] + * @var list */ protected $allowedFields = ['actor_id', 'comment_id']; /** - * @var string + * @var class-string */ protected $returnType = Like::class; @@ -45,18 +45,19 @@ class LikeModel extends UuidModel */ protected $useTimestamps = true; - protected $updatedField; + protected $updatedField = ''; public function addLike(Actor $actor, EpisodeComment $comment, bool $registerActivity = true): void { $this->db->transStart(); $this->insert([ - 'actor_id' => $actor->id, + 'actor_id' => $actor->id, 'comment_id' => $comment->id, ]); - (new EpisodeCommentModel())->builder() + new EpisodeCommentModel() + ->builder() ->where('id', service('uuid')->fromString($comment->id)->getBytes()) ->increment('likes_count'); @@ -65,7 +66,7 @@ class LikeModel extends UuidModel $likeActivity->set('actor', $actor->uri) ->set('object', $comment->uri); - $activityId = model(ActivityModel::class) + $activityId = model('ActivityModel') ->newActivity( 'Like', $actor->id, @@ -78,7 +79,7 @@ class LikeModel extends UuidModel $likeActivity->set('id', url_to('activity', esc($actor->username), $activityId)); - model(ActivityModel::class) + model('ActivityModel') ->update($activityId, [ 'payload' => $likeActivity->toJSON(), ]); @@ -91,12 +92,13 @@ class LikeModel extends UuidModel { $this->db->transStart(); - (new EpisodeCommentModel())->builder() + new EpisodeCommentModel() + ->builder() ->where('id', service('uuid') ->fromString($comment->id) ->getBytes()) ->decrement('likes_count'); $this->where([ - 'actor_id' => $actor->id, + 'actor_id' => $actor->id, 'comment_id' => service('uuid') ->fromString($comment->id) ->getBytes(), @@ -106,13 +108,18 @@ class LikeModel extends UuidModel if ($registerActivity) { $undoActivity = new UndoActivity(); // FIXME: get like activity associated with the deleted like - $activity = model(ActivityModel::class) + $activity = model('ActivityModel') ->where([ - 'type' => 'Like', + 'type' => 'Like', 'actor_id' => $actor->id, ]) ->first(); + if (! $activity instanceof Activity) { + // no like activity found, do nothing + return; + } + $likeActivity = new LikeActivity(); $likeActivity ->set('id', url_to('activity', esc($actor->username), $activity->id)) @@ -123,7 +130,7 @@ class LikeModel extends UuidModel ->set('actor', $actor->uri) ->set('object', $likeActivity); - $activityId = model(ActivityModel::class) + $activityId = model('ActivityModel') ->newActivity( 'Undo', $actor->id, @@ -136,7 +143,7 @@ class LikeModel extends UuidModel $undoActivity->set('id', url_to('activity', esc($actor->username), $activityId)); - model(ActivityModel::class) + model('ActivityModel') ->update($activityId, [ 'payload' => $undoActivity->toJSON(), ]); @@ -152,11 +159,11 @@ class LikeModel extends UuidModel { if ( $this->where([ - 'actor_id' => $actor->id, + 'actor_id' => $actor->id, 'comment_id' => service('uuid') ->fromString($comment->id) ->getBytes(), - ])->first() + ])->first() instanceof Like ) { $this->removeLike($actor, $comment); } else { diff --git a/app/Models/PageModel.php b/app/Models/PageModel.php index 62772b59..f9a9eb5a 100644 --- a/app/Models/PageModel.php +++ b/app/Models/PageModel.php @@ -26,12 +26,12 @@ class PageModel extends Model protected $primaryKey = 'id'; /** - * @var string[] + * @var list */ protected $allowedFields = ['id', 'title', 'slug', 'content_markdown', 'content_html']; /** - * @var string + * @var class-string */ protected $returnType = Page::class; @@ -49,26 +49,25 @@ class PageModel extends Model * @var array */ protected $validationRules = [ - 'title' => 'required', - 'slug' => - 'required|regex_match[/^[a-zA-Z0-9\-]{1,128}$/]|is_unique[pages.slug,id,{id}]', + 'title' => 'required', + 'slug' => 'required|regex_match[/^[a-zA-Z0-9\-]{1,128}$/]|is_unique[pages.slug,id,{id}]', 'content_markdown' => 'required', ]; /** - * @var string[] + * @var list */ protected $afterInsert = ['clearCache']; /** * Before update because slug or title might change * - * @var string[] + * @var list */ protected $beforeUpdate = ['clearCache']; /** - * @var string[] + * @var list */ protected $beforeDelete = ['clearCache']; diff --git a/app/Models/PersonModel.php b/app/Models/PersonModel.php index 9948393c..705ad50a 100644 --- a/app/Models/PersonModel.php +++ b/app/Models/PersonModel.php @@ -26,7 +26,7 @@ class PersonModel extends Model protected $primaryKey = 'id'; /** - * @var string[] + * @var list */ protected $allowedFields = [ 'id', @@ -39,7 +39,7 @@ class PersonModel extends Model ]; /** - * @var string + * @var class-string */ protected $returnType = Person::class; @@ -57,27 +57,26 @@ class PersonModel extends Model * @var array */ protected $validationRules = [ - 'full_name' => 'required', - 'unique_name' => - 'required|regex_match[/^[a-z0-9\-]{1,32}$/]|is_unique[persons.unique_name,id,{id}]', - 'created_by' => 'required', - 'updated_by' => 'required', + 'full_name' => 'required', + 'unique_name' => 'required|regex_match[/^[a-z0-9\-]{1,32}$/]|is_unique[persons.unique_name,id,{id}]', + 'created_by' => 'required', + 'updated_by' => 'required', ]; /** - * @var string[] + * @var list */ protected $afterInsert = ['clearCache']; /** * clear cache before update if by any chance, the person name changes, so will the person link * - * @var string[] + * @var list */ protected $beforeUpdate = ['clearCache']; /** - * @var string[] + * @var list */ protected $beforeDelete = ['clearCache']; @@ -146,8 +145,11 @@ class PersonModel extends Model $this->select('`id`, `full_name`') ->orderBy('`full_name`', 'ASC') ->findAll(), - static function ($result, $person) { - $result[$person->id] = $person->full_name; + static function (array $result, Person $person): array { + $result[] = [ + 'value' => $person->id, + 'label' => $person->full_name, + ]; return $result; }, [], @@ -175,9 +177,10 @@ class PersonModel extends Model if (! ($options = cache($cacheName))) { foreach ($personsTaxonomy as $group_key => $group) { foreach ($group['roles'] as $role_key => $role) { - $options[ - "{$group_key},{$role_key}" - ] = "{$group['label']} › {$role['label']}"; + $options[] = [ + 'value' => sprintf('%s,%s', $group_key, $role_key), + 'label' => sprintf('%s › %s', $group['label'], $role['label']), + ]; } } @@ -191,12 +194,13 @@ class PersonModel extends Model public function addPerson(string $fullName, ?string $informationUrl, string $image): int | bool { $person = new Person([ - 'full_name' => $fullName, - 'unique_name' => slugify($fullName), + 'created_by' => user_id(), + 'updated_by' => user_id(), + 'full_name' => $fullName, + 'unique_name' => slugify($fullName), 'information_url' => $informationUrl, - 'image' => download_file($image), - 'created_by' => user_id(), - 'updated_by' => user_id(), + 'image' => download_file($image), + ]); return $this->insert($person); @@ -211,7 +215,7 @@ class PersonModel extends Model if (! ($found = cache($cacheName))) { $this->builder() ->select( - 'persons.*, episodes_persons.podcast_id as podcast_id, episodes_persons.episode_id as episode_id' + 'persons.*, episodes_persons.podcast_id as podcast_id, episodes_persons.episode_id as episode_id', ) ->distinct() ->join('episodes_persons', 'persons.id = episodes_persons.person_id') @@ -253,34 +257,35 @@ class PersonModel extends Model int $episodeId, int $personId, string $groupSlug, - string $roleSlug + string $roleSlug, ): bool { return $this->db->table('episodes_persons') ->insert([ - 'podcast_id' => $podcastId, - 'episode_id' => $episodeId, - 'person_id' => $personId, + 'podcast_id' => $podcastId, + 'episode_id' => $episodeId, + 'person_id' => $personId, 'person_group' => $groupSlug, - 'person_role' => $roleSlug, + 'person_role' => $roleSlug, ]); } public function addPodcastPerson(int $podcastId, int $personId, string $groupSlug, string $roleSlug): bool { return $this->db->table('podcasts_persons') + ->ignore(true) ->insert([ - 'podcast_id' => $podcastId, - 'person_id' => $personId, + 'podcast_id' => $podcastId, + 'person_id' => $personId, 'person_group' => $groupSlug, - 'person_role' => $roleSlug, + 'person_role' => $roleSlug, ]); } /** * Add persons to podcast * - * @param array $personIds - * @param array $roles + * @param int[] $personIds + * @param string[] $roles * * @return bool|int Number of rows inserted or FALSE on failure */ @@ -292,26 +297,30 @@ class PersonModel extends Model cache() ->delete("podcast#{$podcastId}_persons"); - (new PodcastModel())->clearCache([ - 'id' => $podcastId, - ]); + new PodcastModel() + ->clearCache([ + 'id' => $podcastId, + ]); $data = []; foreach ($personIds as $personId) { if ($roles === []) { + // add to default group (cast) and role (host), see https://podcastindex.org/namespace/1.0#person $data[] = [ - 'podcast_id' => $podcastId, - 'person_id' => $personId, + 'podcast_id' => $podcastId, + 'person_id' => $personId, + 'person_group' => 'cast', + 'person_role' => 'host', ]; } foreach ($roles as $role) { $groupRole = explode(',', $role); $data[] = [ - 'podcast_id' => $podcastId, - 'person_id' => $personId, + 'podcast_id' => $podcastId, + 'person_id' => $personId, 'person_group' => $groupRole[0], - 'person_role' => $groupRole[1], + 'person_role' => $groupRole[1], ]; } } @@ -331,14 +340,15 @@ class PersonModel extends Model cache()->deleteMatching("podcast#{$podcastId}_person#{$personId}*"); cache() ->delete("podcast#{$podcastId}_persons"); - (new PodcastModel())->clearCache([ - 'id' => $podcastId, - ]); + new PodcastModel() + ->clearCache([ + 'id' => $podcastId, + ]); return $this->db->table('podcasts_persons') ->delete([ 'podcast_id' => $podcastId, - 'person_id' => $personId, + 'person_id' => $personId, ]); } @@ -346,41 +356,40 @@ class PersonModel extends Model * Add persons to episode * * @param int[] $personIds - * @param string[] $groupsRoles + * @param string[] $roles * * @return bool|int Number of rows inserted or FALSE on failure */ - public function addEpisodePersons( - int $podcastId, - int $episodeId, - array $personIds, - array $groupsRoles - ): bool | int { + public function addEpisodePersons(int $podcastId, int $episodeId, array $personIds, array $roles): bool | int + { if ($personIds !== []) { cache() ->delete("podcast#{$podcastId}_episode#{$episodeId}_persons"); - (new EpisodeModel())->clearCache([ - 'id' => $episodeId, - ]); + new EpisodeModel() + ->clearCache([ + 'id' => $episodeId, + ]); $data = []; foreach ($personIds as $personId) { - if ($groupsRoles !== []) { - foreach ($groupsRoles as $groupRole) { - $groupRole = explode(',', $groupRole); - $data[] = [ - 'podcast_id' => $podcastId, - 'episode_id' => $episodeId, - 'person_id' => $personId, - 'person_group' => $groupRole[0], - 'person_role' => $groupRole[1], - ]; - } - } else { + if ($roles === []) { $data[] = [ - 'podcast_id' => $podcastId, - 'episode_id' => $episodeId, - 'person_id' => $personId, + 'podcast_id' => $podcastId, + 'episode_id' => $episodeId, + 'person_id' => $personId, + 'person_group' => 'cast', + 'person_role' => 'host', + ]; + } + + foreach ($roles as $role) { + $groupRole = explode(',', $role); + $data[] = [ + 'podcast_id' => $podcastId, + 'episode_id' => $episodeId, + 'person_id' => $personId, + 'person_group' => $groupRole[0], + 'person_role' => $groupRole[1], ]; } } @@ -398,15 +407,16 @@ class PersonModel extends Model cache()->deleteMatching("podcast#{$podcastId}_episode#{$episodeId}_person#{$personId}*"); cache() ->delete("podcast#{$podcastId}_episode#{$episodeId}_persons"); - (new EpisodeModel())->clearCache([ - 'id' => $episodeId, - ]); + new EpisodeModel() + ->clearCache([ + 'id' => $episodeId, + ]); return $this->db->table('episodes_persons') ->delete([ 'podcast_id' => $podcastId, 'episode_id' => $episodeId, - 'person_id' => $personId, + 'person_id' => $personId, ]); } diff --git a/app/Models/PlatformModel.php b/app/Models/PlatformModel.php deleted file mode 100644 index 990f4f59..00000000 --- a/app/Models/PlatformModel.php +++ /dev/null @@ -1,209 +0,0 @@ -baseURL, '/'); - $found = $this->select( - "*, CONCAT('{$baseUrl}/assets/images/platforms/',`type`,'/',`slug`,'.svg') as icon", - )->findAll(); - cache() - ->save('platforms', $found, DECADE); - } - - return $found; - } - - public function getPlatform(string $slug): ?Platform - { - $cacheName = "platform-{$slug}"; - if (! ($found = cache($cacheName))) { - $found = $this->where('slug', $slug) - ->first(); - cache() - ->save($cacheName, $found, DECADE); - } - - return $found; - } - - public function createPlatform( - string $slug, - string $type, - string $label, - string $homeUrl, - string $submitUrl = null - ): int | false { - $data = [ - 'slug' => $slug, - 'type' => $type, - 'label' => $label, - 'home_url' => $homeUrl, - 'submit_url' => $submitUrl, - ]; - - return $this->insert($data, false); - } - - /** - * @return Platform[] - */ - public function getPlatformsWithLinks(int $podcastId, string $platformType): array - { - if ( - ! ($found = cache("podcast#{$podcastId}_platforms_{$platformType}_withLinks")) - ) { - $found = $this->select( - 'platforms.*, podcasts_platforms.link_url, podcasts_platforms.account_id, podcasts_platforms.is_visible, podcasts_platforms.is_on_embed', - ) - ->join( - 'podcasts_platforms', - "podcasts_platforms.platform_slug = platforms.slug AND podcasts_platforms.podcast_id = {$podcastId}", - 'left', - ) - ->where('platforms.type', $platformType) - ->findAll(); - - cache() - ->save("podcast#{$podcastId}_platforms_{$platformType}_withLinks", $found, DECADE); - } - - return $found; - } - - /** - * @return Platform[] - */ - public function getPodcastPlatforms(int $podcastId, string $platformType): array - { - $cacheName = "podcast#{$podcastId}_platforms_{$platformType}"; - if (! ($found = cache($cacheName))) { - $found = $this->select( - 'platforms.*, podcasts_platforms.link_url, podcasts_platforms.account_id, podcasts_platforms.is_visible, podcasts_platforms.is_on_embed', - ) - ->join('podcasts_platforms', 'podcasts_platforms.platform_slug = platforms.slug') - ->where('podcasts_platforms.podcast_id', $podcastId) - ->where('platforms.type', $platformType) - ->findAll(); - - cache() - ->save($cacheName, $found, DECADE); - } - - return $found; - } - - /** - * @param mixed[] $podcastsPlatformsData - */ - public function savePodcastPlatforms( - int $podcastId, - string $platformType, - array $podcastsPlatformsData - ): int | false { - $this->clearCache($podcastId); - - $podcastsPlatformsTable = $this->db->prefixTable('podcasts_platforms'); - $platformsTable = $this->db->prefixTable('platforms'); - - $deleteJoinQuery = <<db->query($deleteJoinQuery, [$podcastId, $platformType]); - - return $this->db - ->table('podcasts_platforms') - ->insertBatch($podcastsPlatformsData); - } - - /** - * @param mixed[] $podcastsPlatformsData - */ - public function createPodcastPlatforms(int $podcastId, array $podcastsPlatformsData): int | false - { - $this->clearCache($podcastId); - - return $this->db - ->table('podcasts_platforms') - ->insertBatch($podcastsPlatformsData); - } - - public function removePodcastPlatform(int $podcastId, string $platformSlug): bool | string - { - $this->clearCache($podcastId); - - return $this->db->table('podcasts_platforms') - ->delete([ - 'podcast_id' => $podcastId, - 'platform_slug' => $platformSlug, - ]); - } - - public function clearCache(int $podcastId): void - { - cache()->deleteMatching("podcast#{$podcastId}_platforms_*"); - - // delete localized podcast page cache - cache() - ->deleteMatching("page_podcast#{$podcastId}*"); - // delete post and episode comments pages cache - cache() - ->deleteMatching('page_post*'); - cache() - ->deleteMatching('page_episode#*'); - } -} diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php index 78d52672..c64516f1 100644 --- a/app/Models/PodcastModel.php +++ b/app/Models/PodcastModel.php @@ -10,8 +10,8 @@ declare(strict_types=1); namespace App\Models; +use App\Entities\Actor; use App\Entities\Podcast; -use CodeIgniter\Database\Query; use CodeIgniter\HTTP\URI; use CodeIgniter\Model; use phpseclib\Crypt\RSA; @@ -29,7 +29,7 @@ class PodcastModel extends Model protected $primaryKey = 'id'; /** - * @var string[] + * @var list */ protected $allowedFields = [ 'id', @@ -38,8 +38,6 @@ class PodcastModel extends Model 'handle', 'description_markdown', 'description_html', - 'episode_description_footer_markdown', - 'episode_description_footer_html', 'cover_id', 'banner_id', 'language_code', @@ -58,12 +56,7 @@ class PodcastModel extends Model 'location_name', 'location_geo', 'location_osm', - 'payment_pointer', - 'custom_rss', 'is_published_on_hubs', - 'partner_id', - 'partner_link_url', - 'partner_image_url', 'is_premium_by_default', 'published_at', 'created_by', @@ -71,7 +64,7 @@ class PodcastModel extends Model ]; /** - * @var string + * @var class-string */ protected $returnType = Podcast::class; @@ -84,45 +77,45 @@ class PodcastModel extends Model * @var array */ protected $validationRules = [ - 'title' => 'required', - 'handle' => - 'required|regex_match[/^[a-zA-Z0-9\_]{1,32}$/]|is_unique[podcasts.handle,id,{id}]', + 'id' => 'permit_empty|is_natural_no_zero', + 'title' => 'required', + 'handle' => 'required|regex_match[/^[a-zA-Z0-9\_]{1,32}$/]|is_unique[podcasts.handle,id,{id}]', 'description_markdown' => 'required', - 'cover_id' => 'required', - 'language_code' => 'required', - 'category_id' => 'required', - 'owner_email' => 'required|valid_email', - 'new_feed_url' => 'valid_url_strict|permit_empty', - 'type' => 'required', - 'published_at' => 'valid_date|permit_empty', - 'created_by' => 'required', - 'updated_by' => 'required', + 'cover_id' => 'required', + 'language_code' => 'required', + 'category_id' => 'required', + 'owner_email' => 'required|valid_email', + 'new_feed_url' => 'valid_url_strict|permit_empty', + 'type' => 'required', + 'published_at' => 'valid_date|permit_empty', + 'created_by' => 'required', + 'updated_by' => 'required', ]; /** - * @var string[] + * @var list */ protected $beforeInsert = ['setPodcastGUID', 'createPodcastActor']; /** - * @var string[] + * @var list */ protected $afterInsert = ['setActorAvatar']; /** - * @var string[] + * @var list */ protected $afterUpdate = ['updatePodcastActor']; /** * clear cache before update if by any chance, the podcast name changes, so will the podcast link * - * @var string[] + * @var list */ protected $beforeUpdate = ['clearCache']; /** - * @var string[] + * @var list */ protected $beforeDelete = ['clearCache']; @@ -170,27 +163,21 @@ class PodcastModel extends Model /** * @return Podcast[] */ - public function getAllPodcasts(string $orderBy = null): array + public function getAllPodcasts(?string $orderBy = null): array { $prefix = $this->db->getPrefix(); if ($orderBy === 'activity') { - $fediverseTablePrefix = $prefix . config('Fediverse') - ->tablesPrefix; $this->builder() - ->select( - 'podcasts.*, MAX(' . $fediverseTablePrefix . 'posts.published_at' . ') as max_published_at' - ) - ->join( - $fediverseTablePrefix . 'posts', - $fediverseTablePrefix . 'posts.actor_id = podcasts.actor_id', - 'left' - ) + ->select('podcasts.*, MAX(`' . $prefix . 'fediverse_posts`.`published_at`) as max_published_at') + ->join('fediverse_posts', 'fediverse_posts.actor_id = podcasts.actor_id', 'left') + ->groupStart() ->where( - '`' . $fediverseTablePrefix . 'posts`.`published_at` <= UTC_TIMESTAMP()', + '`' . $prefix . 'fediverse_posts`.`published_at` <= UTC_TIMESTAMP()', null, - false - )->orWhere($fediverseTablePrefix . 'posts.published_at', null) + false, + )->orWhere('fediverse_posts.published_at') + ->groupEnd() ->groupBy('podcasts.actor_id') ->orderBy('max_published_at', 'DESC'); } elseif ($orderBy === 'created_desc') { @@ -205,15 +192,14 @@ class PodcastModel extends Model /** * Gets all the podcasts a given user is contributing to * + * @param string[] $userPodcastIds * @return Podcast[] podcasts */ - public function getUserPodcasts(int $userId): array + public function getUserPodcasts(int $userId, array $userPodcastIds): array { $cacheName = "user{$userId}_podcasts"; if (! ($found = cache($cacheName))) { - $found = $this->select('podcasts.*') - ->join('podcasts_users', 'podcasts_users.podcast_id = podcasts.id') - ->where('podcasts_users.user_id', $userId) + $found = $userPodcastIds === [] ? [] : $this->whereIn('id', $userPodcastIds) ->findAll(); cache() @@ -223,76 +209,18 @@ class PodcastModel extends Model return $found; } - public function addPodcastContributor(int $userId, int $podcastId, int $groupId): Query | bool + public function getContributorGroup(int $userId, int $podcastId): int | false { - cache()->delete("podcast#{$podcastId}_contributors"); - - $data = [ - 'user_id' => $userId, - 'podcast_id' => $podcastId, - 'group_id' => $groupId, - ]; - - return $this->db->table('podcasts_users') - ->insert($data); - } - - public function updatePodcastContributor(int $userId, int $podcastId, int $groupId): bool - { - cache()->delete("podcast#{$podcastId}_contributors"); - - return $this->db - ->table('podcasts_users') - ->where([ - 'user_id' => $userId, - 'podcast_id' => $podcastId, - ]) - ->update([ - 'group_id' => $groupId, - ]); - } - - public function removePodcastContributor(int $userId, int $podcastId): string | bool - { - cache()->delete("podcast#{$podcastId}_contributors"); - - return $this->db - ->table('podcasts_users') - ->where([ - 'user_id' => $userId, - 'podcast_id' => $podcastId, - ]) - ->delete(); - } - - public function getContributorGroupId(int $userId, int | string $podcastId): int | false - { - if (! is_numeric($podcastId)) { - // identifier is the podcast name, request must be a join - $userPodcast = $this->db - ->table('podcasts_users') - ->select('group_id, user_id') - ->join('podcasts', 'podcasts.id = podcasts_users.podcast_id') - ->where([ - 'user_id' => $userId, - 'handle' => $podcastId, - ]) - ->get() - ->getResultObject(); - } else { - $userPodcast = $this->db - ->table('podcasts_users') - ->select('group_id') - ->where([ - 'user_id' => $userId, - 'podcast_id' => $podcastId, - ]) - ->get() - ->getResultObject(); - } + $userPodcast = $this->db + ->table('auth_groups_users') + ->select('user_id, group') + ->where('user_id', $userId) + ->like('group', "podcast#{$podcastId}") + ->get() + ->getResultObject(); return $userPodcast !== [] - ? (int) $userPodcast[0]->group_id + ? (int) $userPodcast[0]->group : false; } @@ -308,9 +236,8 @@ class PodcastModel extends Model ->builder() ->select('YEAR(published_at) as year, count(*) as number_of_episodes') ->where([ - 'podcast_id' => $podcastId, + 'podcast_id' => $podcastId, 'season_number' => null, - 'published_at IS NOT' => null, ]) ->where('`published_at` <= UTC_TIMESTAMP()', null, false) ->groupBy('year') @@ -321,13 +248,7 @@ class PodcastModel extends Model $secondsToNextUnpublishedEpisode = $episodeModel->getSecondsToNextUnpublishedEpisode($podcastId); cache() - ->save( - $cacheName, - $found, - $secondsToNextUnpublishedEpisode - ? $secondsToNextUnpublishedEpisode - : DECADE, - ); + ->save($cacheName, $found, $secondsToNextUnpublishedEpisode ?: DECADE); } return $found; @@ -345,9 +266,8 @@ class PodcastModel extends Model ->builder() ->select('season_number, count(*) as number_of_episodes') ->where([ - 'podcast_id' => $podcastId, + 'podcast_id' => $podcastId, 'season_number is not' => null, - 'published_at IS NOT' => null, ]) ->where('`published_at` <= UTC_TIMESTAMP()', null, false) ->groupBy('season_number') @@ -358,13 +278,7 @@ class PodcastModel extends Model $secondsToNextUnpublishedEpisode = $episodeModel->getSecondsToNextUnpublishedEpisode($podcastId); cache() - ->save( - $cacheName, - $found, - $secondsToNextUnpublishedEpisode - ? $secondsToNextUnpublishedEpisode - : DECADE, - ); + ->save($cacheName, $found, $secondsToNextUnpublishedEpisode ?: DECADE); } return $found; @@ -395,14 +309,11 @@ class PodcastModel extends Model ]; } - $secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode($podcastId); + $secondsToNextUnpublishedEpisode = new EpisodeModel() + ->getSecondsToNextUnpublishedEpisode($podcastId); cache() - ->save( - $cacheName, - $defaultQuery, - $secondsToNextUnpublishedEpisode ? $secondsToNextUnpublishedEpisode : DECADE - ); + ->save($cacheName, $defaultQuery, $secondsToNextUnpublishedEpisode ?: DECADE); } return $defaultQuery; @@ -415,13 +326,14 @@ class PodcastModel extends Model */ public function clearCache(array $data): array { - $podcast = (new self())->getPodcastById(is_array($data['id']) ? $data['id'][0] : $data['id']); + $podcast = new self() + ->find((int) (is_array($data['id']) ? $data['id'][0] : $data['id'])); // delete cache for users' podcasts cache() ->deleteMatching('user*podcasts'); - if ($podcast !== null) { + if ($podcast instanceof Podcast) { // delete cache all podcast pages cache() ->deleteMatching("page_podcast#{$podcast->id}*"); @@ -444,6 +356,19 @@ class PodcastModel extends Model return $data; } + public function getFullTextMatchClauseForPodcasts(string $table, string $value): string + { + return ' + MATCH ( + ' . $table . '.title , + ' . $table . '.description_markdown, + ' . $table . '.handle, + ' . $table . '.location_name + ) + AGAINST(' . $this->db->escape($value) . ') + '; + } + /** * Creates an actor linked to the podcast (Triggered before insert) * @@ -466,21 +391,22 @@ class PodcastModel extends Model $domain = $url->getHost() . ($url->getPort() ? ':' . $url->getPort() : ''); - $actorId = (new ActorModel())->insert( - [ - 'uri' => url_to('actor', $username), - 'username' => $username, - 'domain' => $domain, - 'private_key' => $privatekey, - 'public_key' => $publickey, - 'display_name' => $data['data']['title'], - 'summary' => $data['data']['description_html'], - 'inbox_url' => url_to('inbox', $username), - 'outbox_url' => url_to('outbox', $username), - 'followers_url' => url_to('followers', $username), - ], - true, - ); + $actorId = new ActorModel() + ->insert( + [ + 'uri' => url_to('podcast-activity', $username), + 'username' => $username, + 'domain' => $domain, + 'private_key' => $privatekey, + 'public_key' => $publickey, + 'display_name' => $data['data']['title'], + 'summary' => $data['data']['description_html'], + 'inbox_url' => url_to('inbox', $username), + 'outbox_url' => url_to('outbox', $username), + 'followers_url' => url_to('followers', $username), + ], + true, + ); $data['data']['actor_id'] = $actorId; @@ -494,17 +420,22 @@ class PodcastModel extends Model */ protected function setActorAvatar(array $data): array { - $podcast = (new self())->getPodcastById(is_array($data['id']) ? $data['id'][0] : $data['id']); + $podcast = new self() + ->find((int) (is_array($data['id']) ? $data['id'][0] : $data['id'])); - if ($podcast !== null) { - $podcastActor = (new ActorModel())->find($podcast->actor_id); + if ($podcast instanceof Podcast) { + $podcastActor = new ActorModel() + ->find($podcast->actor_id); - if ($podcastActor) { - $podcastActor->avatar_image_url = $podcast->cover->thumbnail_url; - $podcastActor->avatar_image_mimetype = $podcast->cover->thumbnail_mimetype; - - (new ActorModel())->update($podcast->actor_id, $podcastActor); + if (! $podcastActor instanceof Actor) { + return $data; } + + $podcastActor->avatar_image_url = $podcast->cover->federation_url; + $podcastActor->avatar_image_mimetype = $podcast->cover->federation_mimetype; + + new ActorModel() + ->update($podcast->actor_id, $podcastActor); } return $data; @@ -517,20 +448,21 @@ class PodcastModel extends Model */ protected function updatePodcastActor(array $data): array { - $podcast = (new self())->getPodcastById(is_array($data['id']) ? $data['id'][0] : $data['id']); + $podcast = new self() + ->find((int) (is_array($data['id']) ? $data['id'][0] : $data['id'])); - if ($podcast !== null) { + if ($podcast instanceof Podcast) { $actorModel = new ActorModel(); $actor = $actorModel->getActorById($podcast->actor_id); - if ($actor !== null) { + if ($actor instanceof Actor) { // update values $actor->display_name = $podcast->title; $actor->summary = $podcast->description_html; $actor->avatar_image_url = $podcast->cover->federation_url; - $actor->avatar_image_mimetype = $podcast->cover->file_mimetype; - $actor->cover_image_url = $podcast->banner->federation_url; - $actor->cover_image_mimetype = $podcast->banner->file_mimetype; + $actor->avatar_image_mimetype = $podcast->cover->federation_mimetype; + $actor->cover_image_url = get_podcast_banner_url($podcast, 'federation'); + $actor->cover_image_mimetype = get_podcast_banner_mimetype($podcast, 'federation'); if ($actor->hasChanged()) { $actorModel->update($actor->id, $actor); @@ -544,13 +476,21 @@ class PodcastModel extends Model /** * @param mixed[] $data * + * Sets the UUIDv5 for a podcast. For more information, see + * https://podcastindex.org/namespace/1.0#guid + * * @return mixed[] */ protected function setPodcastGUID(array $data): array { - if (! array_key_exists('guid', $data['data']) || $data['data']['guid'] === null) { - helper('misc'); - $data['data']['guid'] = podcast_uuid(url_to('podcast-rss-feed', $data['data']['handle'])); + if (! array_key_exists( + 'guid', + $data['data'], + ) || $data['data']['guid'] === null || $data['data']['guid'] === '') { + $uuid = service('uuid'); + $feedUrl = url_to('podcast-rss-feed', $data['data']['handle']); + // 'ead4c236-bf58-58c6-a2c6-a6b28d128cb6' is the uuid of the podcast namespace + $data['data']['guid'] = $uuid->uuid5('ead4c236-bf58-58c6-a2c6-a6b28d128cb6', $feedUrl)->toString(); } return $data; diff --git a/app/Models/PostModel.php b/app/Models/PostModel.php index 6f8df615..06b0b00a 100644 --- a/app/Models/PostModel.php +++ b/app/Models/PostModel.php @@ -16,12 +16,12 @@ use Modules\Fediverse\Models\PostModel as FediversePostModel; class PostModel extends FediversePostModel { /** - * @var string + * @var class-string */ protected $returnType = Post::class; /** - * @var string[] + * @var list */ protected $allowedFields = [ 'id', @@ -32,6 +32,7 @@ class PostModel extends FediversePostModel 'episode_id', 'message', 'message_html', + 'is_private', 'favourites_count', 'reblogs_count', 'replies_count', @@ -49,7 +50,7 @@ class PostModel extends FediversePostModel return $this->where([ 'episode_id' => $episodeId, ]) - ->where('in_reply_to_id', null) + ->where('in_reply_to_id') ->where('`published_at` <= UTC_TIMESTAMP()', null, false) ->orderBy('published_at', 'DESC') ->findAll(); @@ -58,13 +59,13 @@ class PostModel extends FediversePostModel public function setEpisodeIdForRepliesOfEpisodePosts(): int | false { // make sure that posts in reply to episode activities have an episode id - $postsToUpdate = $this->db->table(config('Fediverse')->tablesPrefix . 'posts as p1') - ->join(config('Fediverse')->tablesPrefix . 'posts as p2', 'p1.id = p2.in_reply_to_id') + $postsToUpdate = $this->db->table('fediverse_posts as p1') + ->join('fediverse_posts as p2', 'p1.id = p2.in_reply_to_id') ->select('p2.id, p1.episode_id') ->where([ 'p2.in_reply_to_id IS NOT' => null, - 'p2.episode_id' => null, - 'p1.episode_id IS NOT' => null, + 'p2.episode_id' => null, + 'p1.episode_id IS NOT' => null, ]) ->get() ->getResultArray(); diff --git a/app/Models/UserModel.php b/app/Models/UserModel.php deleted file mode 100644 index 13685b89..00000000 --- a/app/Models/UserModel.php +++ /dev/null @@ -1,55 +0,0 @@ -select('users.*, auth_groups.name as podcast_role') - ->join('podcasts_users', 'podcasts_users.user_id = users.id') - ->join('auth_groups', 'auth_groups.id = podcasts_users.group_id') - ->where('podcasts_users.podcast_id', $podcastId) - ->findAll(); - - cache() - ->save($cacheName, $found, DECADE); - } - - return $found; - } - - public function getPodcastContributor(int $userId, int $podcastId): ?User - { - // @phpstan-ignore-next-line - return $this->select('users.*, podcasts_users.podcast_id as podcast_id, auth_groups.name as podcast_role') - ->join('podcasts_users', 'podcasts_users.user_id = users.id') - ->join('auth_groups', 'auth_groups.id = podcasts_users.group_id') - ->where([ - 'users.id' => $userId, - 'podcast_id' => $podcastId, - ]) - ->first(); - } -} diff --git a/app/Resources/icons/account-circle.svg b/app/Resources/icons/account-circle.svg deleted file mode 100644 index c825dc5a..00000000 --- a/app/Resources/icons/account-circle.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/add-box.svg b/app/Resources/icons/add-box.svg deleted file mode 100755 index 5a6fd80c..00000000 --- a/app/Resources/icons/add-box.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/add.svg b/app/Resources/icons/add.svg deleted file mode 100755 index 8f3f5a20..00000000 --- a/app/Resources/icons/add.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/alert.svg b/app/Resources/icons/alert.svg deleted file mode 100755 index 7dd74af7..00000000 --- a/app/Resources/icons/alert.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/arrow-left.svg b/app/Resources/icons/arrow-left.svg deleted file mode 100644 index d10d02b5..00000000 --- a/app/Resources/icons/arrow-left.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/arrow-right.svg b/app/Resources/icons/arrow-right.svg deleted file mode 100644 index f46779f7..00000000 --- a/app/Resources/icons/arrow-right.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/at.svg b/app/Resources/icons/at.svg deleted file mode 100644 index 80191b5f..00000000 --- a/app/Resources/icons/at.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/bold.svg b/app/Resources/icons/bold.svg deleted file mode 100644 index b5fb5947..00000000 --- a/app/Resources/icons/bold.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/bookmark.svg b/app/Resources/icons/bookmark.svg deleted file mode 100755 index d3bde5f3..00000000 --- a/app/Resources/icons/bookmark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/calendar.svg b/app/Resources/icons/calendar.svg deleted file mode 100644 index fe9bf8d9..00000000 --- a/app/Resources/icons/calendar.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/caret-down.svg b/app/Resources/icons/caret-down.svg deleted file mode 100644 index e2138c8d..00000000 --- a/app/Resources/icons/caret-down.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/caret-right.svg b/app/Resources/icons/caret-right.svg deleted file mode 100644 index 346cb156..00000000 --- a/app/Resources/icons/caret-right.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/chat.svg b/app/Resources/icons/chat.svg deleted file mode 100755 index 594b1503..00000000 --- a/app/Resources/icons/chat.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/check.svg b/app/Resources/icons/check.svg deleted file mode 100644 index a28368fc..00000000 --- a/app/Resources/icons/check.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/chevron-left.svg b/app/Resources/icons/chevron-left.svg deleted file mode 100644 index 6d82f7ba..00000000 --- a/app/Resources/icons/chevron-left.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/chevron-right.svg b/app/Resources/icons/chevron-right.svg deleted file mode 100644 index f33dff5e..00000000 --- a/app/Resources/icons/chevron-right.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/clapperboard.svg b/app/Resources/icons/clapperboard.svg deleted file mode 100644 index c5d7d121..00000000 --- a/app/Resources/icons/clapperboard.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/clipboard.svg b/app/Resources/icons/clipboard.svg deleted file mode 100644 index 4e4214b2..00000000 --- a/app/Resources/icons/clipboard.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/close.svg b/app/Resources/icons/close.svg deleted file mode 100644 index 0ef4f305..00000000 --- a/app/Resources/icons/close.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/cloud-off.svg b/app/Resources/icons/cloud-off.svg deleted file mode 100755 index 7177145a..00000000 --- a/app/Resources/icons/cloud-off.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/dashboard.svg b/app/Resources/icons/dashboard.svg deleted file mode 100644 index a25c9e47..00000000 --- a/app/Resources/icons/dashboard.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/database.svg b/app/Resources/icons/database.svg deleted file mode 100644 index 6dc449b4..00000000 --- a/app/Resources/icons/database.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/Resources/icons/delete-bin.svg b/app/Resources/icons/delete-bin.svg deleted file mode 100755 index bd1f9b30..00000000 --- a/app/Resources/icons/delete-bin.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/disc.svg b/app/Resources/icons/disc.svg deleted file mode 100644 index 095d2cda..00000000 --- a/app/Resources/icons/disc.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/download.svg b/app/Resources/icons/download.svg deleted file mode 100755 index b3ea2a9f..00000000 --- a/app/Resources/icons/download.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/edit.svg b/app/Resources/icons/edit.svg deleted file mode 100755 index d9efb56c..00000000 --- a/app/Resources/icons/edit.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/exchange-dollar.svg b/app/Resources/icons/exchange-dollar.svg deleted file mode 100644 index 85cc6af0..00000000 --- a/app/Resources/icons/exchange-dollar.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/Resources/icons/external-link.svg b/app/Resources/icons/external-link.svg deleted file mode 100755 index 2efc6259..00000000 --- a/app/Resources/icons/external-link.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/eye.svg b/app/Resources/icons/eye.svg deleted file mode 100755 index f14a8b7d..00000000 --- a/app/Resources/icons/eye.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/file-copy.svg b/app/Resources/icons/file-copy.svg deleted file mode 100755 index 0b907436..00000000 --- a/app/Resources/icons/file-copy.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/file-download.svg b/app/Resources/icons/file-download.svg deleted file mode 100644 index 0202c99a..00000000 --- a/app/Resources/icons/file-download.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/folder-user.svg b/app/Resources/icons/folder-user.svg deleted file mode 100755 index 6dcd37c4..00000000 --- a/app/Resources/icons/folder-user.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/forbid.svg b/app/Resources/icons/forbid.svg deleted file mode 100644 index dbc2632c..00000000 --- a/app/Resources/icons/forbid.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/funding/default.svg b/app/Resources/icons/funding/default.svg deleted file mode 100644 index 0c7876ae..00000000 --- a/app/Resources/icons/funding/default.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/funding/gofundme.svg b/app/Resources/icons/funding/gofundme.svg deleted file mode 100755 index 976e231a..00000000 --- a/app/Resources/icons/funding/gofundme.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/funding/helloasso.svg b/app/Resources/icons/funding/helloasso.svg deleted file mode 100755 index e32d9a3f..00000000 --- a/app/Resources/icons/funding/helloasso.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/funding/indiegogo.svg b/app/Resources/icons/funding/indiegogo.svg deleted file mode 100755 index beb3ca26..00000000 --- a/app/Resources/icons/funding/indiegogo.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/funding/kickstarter.svg b/app/Resources/icons/funding/kickstarter.svg deleted file mode 100755 index a300d7be..00000000 --- a/app/Resources/icons/funding/kickstarter.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/funding/kisskissbankbank.svg b/app/Resources/icons/funding/kisskissbankbank.svg deleted file mode 100755 index 4755a204..00000000 --- a/app/Resources/icons/funding/kisskissbankbank.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/funding/liberapay.svg b/app/Resources/icons/funding/liberapay.svg deleted file mode 100755 index 827862fc..00000000 --- a/app/Resources/icons/funding/liberapay.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/funding/patreon.svg b/app/Resources/icons/funding/patreon.svg deleted file mode 100755 index 39e20c5a..00000000 --- a/app/Resources/icons/funding/patreon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/funding/paypal.svg b/app/Resources/icons/funding/paypal.svg deleted file mode 100755 index 2b525b0c..00000000 --- a/app/Resources/icons/funding/paypal.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/funding/tipeee.svg b/app/Resources/icons/funding/tipeee.svg deleted file mode 100755 index e3797fee..00000000 --- a/app/Resources/icons/funding/tipeee.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/funding/ulule.svg b/app/Resources/icons/funding/ulule.svg deleted file mode 100755 index c5002f2d..00000000 --- a/app/Resources/icons/funding/ulule.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/group.svg b/app/Resources/icons/group.svg deleted file mode 100755 index 5c2f10ee..00000000 --- a/app/Resources/icons/group.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/heading.svg b/app/Resources/icons/heading.svg deleted file mode 100644 index 0ae177f1..00000000 --- a/app/Resources/icons/heading.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/heart.svg b/app/Resources/icons/heart.svg deleted file mode 100755 index f10aafa4..00000000 --- a/app/Resources/icons/heart.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/history.svg b/app/Resources/icons/history.svg deleted file mode 100644 index 684091df..00000000 --- a/app/Resources/icons/history.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/Resources/icons/home-gear.svg b/app/Resources/icons/home-gear.svg deleted file mode 100644 index 2504da6d..00000000 --- a/app/Resources/icons/home-gear.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/image-add.svg b/app/Resources/icons/image-add.svg deleted file mode 100644 index dca9aa8b..00000000 --- a/app/Resources/icons/image-add.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/italic.svg b/app/Resources/icons/italic.svg deleted file mode 100644 index 4203e07e..00000000 --- a/app/Resources/icons/italic.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/line-chart.svg b/app/Resources/icons/line-chart.svg deleted file mode 100755 index dc43cd7d..00000000 --- a/app/Resources/icons/line-chart.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/link.svg b/app/Resources/icons/link.svg deleted file mode 100755 index 3b7c8e06..00000000 --- a/app/Resources/icons/link.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/list-ordered.svg b/app/Resources/icons/list-ordered.svg deleted file mode 100644 index be282082..00000000 --- a/app/Resources/icons/list-ordered.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/list-unordered.svg b/app/Resources/icons/list-unordered.svg deleted file mode 100644 index 41809a3a..00000000 --- a/app/Resources/icons/list-unordered.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/loader.svg b/app/Resources/icons/loader.svg deleted file mode 100644 index 55da7bdb..00000000 --- a/app/Resources/icons/loader.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/lock-unlock.svg b/app/Resources/icons/lock-unlock.svg deleted file mode 100644 index 0ea4517c..00000000 --- a/app/Resources/icons/lock-unlock.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/lock.svg b/app/Resources/icons/lock.svg deleted file mode 100644 index a54f3e42..00000000 --- a/app/Resources/icons/lock.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/map-pin.svg b/app/Resources/icons/map-pin.svg deleted file mode 100644 index 5950f056..00000000 --- a/app/Resources/icons/map-pin.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/markdown.svg b/app/Resources/icons/markdown.svg deleted file mode 100644 index ab3edd2b..00000000 --- a/app/Resources/icons/markdown.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/menu.svg b/app/Resources/icons/menu.svg deleted file mode 100755 index 666764dc..00000000 --- a/app/Resources/icons/menu.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/mic.svg b/app/Resources/icons/mic.svg deleted file mode 100755 index becff50c..00000000 --- a/app/Resources/icons/mic.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/more.svg b/app/Resources/icons/more.svg deleted file mode 100755 index 5f6b5dba..00000000 --- a/app/Resources/icons/more.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/movie.svg b/app/Resources/icons/movie.svg deleted file mode 100755 index f92dd60f..00000000 --- a/app/Resources/icons/movie.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/notification-bell.svg b/app/Resources/icons/notification-bell.svg deleted file mode 100644 index ea792a4d..00000000 --- a/app/Resources/icons/notification-bell.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/Resources/icons/pages.svg b/app/Resources/icons/pages.svg deleted file mode 100755 index 3d28c400..00000000 --- a/app/Resources/icons/pages.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/pause.svg b/app/Resources/icons/pause.svg deleted file mode 100644 index 81cffce1..00000000 --- a/app/Resources/icons/pause.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/play-circle.svg b/app/Resources/icons/play-circle.svg deleted file mode 100644 index 5d5f7039..00000000 --- a/app/Resources/icons/play-circle.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/Resources/icons/play.svg b/app/Resources/icons/play.svg deleted file mode 100644 index 4978d3d5..00000000 --- a/app/Resources/icons/play.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/amazon.svg b/app/Resources/icons/podcasting/amazon.svg deleted file mode 100755 index 38341ba6..00000000 --- a/app/Resources/icons/podcasting/amazon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/antennapod.svg b/app/Resources/icons/podcasting/antennapod.svg deleted file mode 100755 index 0e0b9006..00000000 --- a/app/Resources/icons/podcasting/antennapod.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/anytime.svg b/app/Resources/icons/podcasting/anytime.svg deleted file mode 100644 index d7e778a7..00000000 --- a/app/Resources/icons/podcasting/anytime.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/apple.svg b/app/Resources/icons/podcasting/apple.svg deleted file mode 100755 index 31e26446..00000000 --- a/app/Resources/icons/podcasting/apple.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/blubrry.svg b/app/Resources/icons/podcasting/blubrry.svg deleted file mode 100755 index a556a5d8..00000000 --- a/app/Resources/icons/podcasting/blubrry.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/breaker.svg b/app/Resources/icons/podcasting/breaker.svg deleted file mode 100755 index 8de3e6e6..00000000 --- a/app/Resources/icons/podcasting/breaker.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/breez.svg b/app/Resources/icons/podcasting/breez.svg deleted file mode 100644 index d0901c86..00000000 --- a/app/Resources/icons/podcasting/breez.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/castamatic.svg b/app/Resources/icons/podcasting/castamatic.svg deleted file mode 100644 index 6c97e493..00000000 --- a/app/Resources/icons/podcasting/castamatic.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/castbox.svg b/app/Resources/icons/podcasting/castbox.svg deleted file mode 100755 index 701e3721..00000000 --- a/app/Resources/icons/podcasting/castbox.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/castopod.svg b/app/Resources/icons/podcasting/castopod.svg deleted file mode 100755 index 784f7818..00000000 --- a/app/Resources/icons/podcasting/castopod.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/castro.svg b/app/Resources/icons/podcasting/castro.svg deleted file mode 100755 index 189e7d5a..00000000 --- a/app/Resources/icons/podcasting/castro.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/chartable.svg b/app/Resources/icons/podcasting/chartable.svg deleted file mode 100755 index 46c49805..00000000 --- a/app/Resources/icons/podcasting/chartable.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/deezer.svg b/app/Resources/icons/podcasting/deezer.svg deleted file mode 100755 index a22f53bf..00000000 --- a/app/Resources/icons/podcasting/deezer.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/default.svg b/app/Resources/icons/podcasting/default.svg deleted file mode 100644 index 09670128..00000000 --- a/app/Resources/icons/podcasting/default.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/fountain.svg b/app/Resources/icons/podcasting/fountain.svg deleted file mode 100644 index 558aea45..00000000 --- a/app/Resources/icons/podcasting/fountain.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/fyyd.svg b/app/Resources/icons/podcasting/fyyd.svg deleted file mode 100755 index 8f93cda4..00000000 --- a/app/Resources/icons/podcasting/fyyd.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/google.svg b/app/Resources/icons/podcasting/google.svg deleted file mode 100755 index e580789c..00000000 --- a/app/Resources/icons/podcasting/google.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/gpodder.svg b/app/Resources/icons/podcasting/gpodder.svg deleted file mode 100644 index a8e02542..00000000 --- a/app/Resources/icons/podcasting/gpodder.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/ivoox.svg b/app/Resources/icons/podcasting/ivoox.svg deleted file mode 100755 index f6c76f4a..00000000 --- a/app/Resources/icons/podcasting/ivoox.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/listennotes.svg b/app/Resources/icons/podcasting/listennotes.svg deleted file mode 100755 index 3f7826f1..00000000 --- a/app/Resources/icons/podcasting/listennotes.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/overcast.svg b/app/Resources/icons/podcasting/overcast.svg deleted file mode 100755 index 3c30ae4a..00000000 --- a/app/Resources/icons/podcasting/overcast.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/playerfm.svg b/app/Resources/icons/podcasting/playerfm.svg deleted file mode 100755 index 4e0adb91..00000000 --- a/app/Resources/icons/podcasting/playerfm.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/plink.svg b/app/Resources/icons/podcasting/plink.svg deleted file mode 100644 index 4f31d3e9..00000000 --- a/app/Resources/icons/podcasting/plink.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/pocketcasts.svg b/app/Resources/icons/podcasting/pocketcasts.svg deleted file mode 100755 index 62b2b533..00000000 --- a/app/Resources/icons/podcasting/pocketcasts.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/podbean.svg b/app/Resources/icons/podcasting/podbean.svg deleted file mode 100755 index 71d160bd..00000000 --- a/app/Resources/icons/podcasting/podbean.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/podcastaddict.svg b/app/Resources/icons/podcasting/podcastaddict.svg deleted file mode 100755 index 83e02432..00000000 --- a/app/Resources/icons/podcasting/podcastaddict.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/podcastguru.svg b/app/Resources/icons/podcasting/podcastguru.svg deleted file mode 100644 index 3d3909fa..00000000 --- a/app/Resources/icons/podcasting/podcastguru.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/podcastindex.svg b/app/Resources/icons/podcasting/podcastindex.svg deleted file mode 100755 index 9c46acc6..00000000 --- a/app/Resources/icons/podcasting/podcastindex.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/podchaser.svg b/app/Resources/icons/podcasting/podchaser.svg deleted file mode 100755 index 5996f9d4..00000000 --- a/app/Resources/icons/podcasting/podchaser.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/podcloud.svg b/app/Resources/icons/podcasting/podcloud.svg deleted file mode 100755 index eee11508..00000000 --- a/app/Resources/icons/podcasting/podcloud.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/podfriend.svg b/app/Resources/icons/podcasting/podfriend.svg deleted file mode 100755 index 7b84e85d..00000000 --- a/app/Resources/icons/podcasting/podfriend.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/podinstall.svg b/app/Resources/icons/podcasting/podinstall.svg deleted file mode 100755 index ccc7c5be..00000000 --- a/app/Resources/icons/podcasting/podinstall.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/podlink.svg b/app/Resources/icons/podcasting/podlink.svg deleted file mode 100755 index e0ad3e83..00000000 --- a/app/Resources/icons/podcasting/podlink.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/podlp.svg b/app/Resources/icons/podcasting/podlp.svg deleted file mode 100644 index f4a9a5c7..00000000 --- a/app/Resources/icons/podcasting/podlp.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/podnews.svg b/app/Resources/icons/podcasting/podnews.svg deleted file mode 100644 index 95f890cc..00000000 --- a/app/Resources/icons/podcasting/podnews.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/podtail.svg b/app/Resources/icons/podcasting/podtail.svg deleted file mode 100755 index b0b10dd9..00000000 --- a/app/Resources/icons/podcasting/podtail.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/podverse.svg b/app/Resources/icons/podcasting/podverse.svg deleted file mode 100755 index 4a710f64..00000000 --- a/app/Resources/icons/podcasting/podverse.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/radiopublic.svg b/app/Resources/icons/podcasting/radiopublic.svg deleted file mode 100755 index de3ff737..00000000 --- a/app/Resources/icons/podcasting/radiopublic.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/sphinxchat.svg b/app/Resources/icons/podcasting/sphinxchat.svg deleted file mode 100644 index 95c2c4c4..00000000 --- a/app/Resources/icons/podcasting/sphinxchat.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/spotify.svg b/app/Resources/icons/podcasting/spotify.svg deleted file mode 100755 index 43d56a18..00000000 --- a/app/Resources/icons/podcasting/spotify.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/spreaker.svg b/app/Resources/icons/podcasting/spreaker.svg deleted file mode 100755 index 30cc454c..00000000 --- a/app/Resources/icons/podcasting/spreaker.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/stitcher.svg b/app/Resources/icons/podcasting/stitcher.svg deleted file mode 100755 index 0d59cba8..00000000 --- a/app/Resources/icons/podcasting/stitcher.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/tsacdop.svg b/app/Resources/icons/podcasting/tsacdop.svg deleted file mode 100644 index 0d3e288f..00000000 --- a/app/Resources/icons/podcasting/tsacdop.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/tunein.svg b/app/Resources/icons/podcasting/tunein.svg deleted file mode 100755 index da721274..00000000 --- a/app/Resources/icons/podcasting/tunein.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/podcasting/zion.svg b/app/Resources/icons/podcasting/zion.svg deleted file mode 100644 index 20c2f2f7..00000000 --- a/app/Resources/icons/podcasting/zion.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/question.svg b/app/Resources/icons/question.svg deleted file mode 100755 index b7fd91ed..00000000 --- a/app/Resources/icons/question.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/quote.svg b/app/Resources/icons/quote.svg deleted file mode 100644 index a83df6c4..00000000 --- a/app/Resources/icons/quote.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/refresh.svg b/app/Resources/icons/refresh.svg deleted file mode 100644 index 08b0dba0..00000000 --- a/app/Resources/icons/refresh.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/repeat.svg b/app/Resources/icons/repeat.svg deleted file mode 100644 index c5a26047..00000000 --- a/app/Resources/icons/repeat.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/rss.svg b/app/Resources/icons/rss.svg deleted file mode 100755 index 723552d9..00000000 --- a/app/Resources/icons/rss.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/scales.svg b/app/Resources/icons/scales.svg deleted file mode 100755 index 7383e06a..00000000 --- a/app/Resources/icons/scales.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/search.svg b/app/Resources/icons/search.svg deleted file mode 100644 index 4d61f480..00000000 --- a/app/Resources/icons/search.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/Resources/icons/send-plane.svg b/app/Resources/icons/send-plane.svg deleted file mode 100644 index 74ffbcf9..00000000 --- a/app/Resources/icons/send-plane.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/settings.svg b/app/Resources/icons/settings.svg deleted file mode 100755 index 893c92d2..00000000 --- a/app/Resources/icons/settings.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/social/castopod.svg b/app/Resources/icons/social/castopod.svg deleted file mode 100755 index 784f7818..00000000 --- a/app/Resources/icons/social/castopod.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/default.svg b/app/Resources/icons/social/default.svg deleted file mode 100644 index 3e1b678a..00000000 --- a/app/Resources/icons/social/default.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/discord.svg b/app/Resources/icons/social/discord.svg deleted file mode 100644 index 9a63df1f..00000000 --- a/app/Resources/icons/social/discord.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/facebook.svg b/app/Resources/icons/social/facebook.svg deleted file mode 100755 index c6ddd7e9..00000000 --- a/app/Resources/icons/social/facebook.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/funkwhale.svg b/app/Resources/icons/social/funkwhale.svg deleted file mode 100755 index 95b5abf6..00000000 --- a/app/Resources/icons/social/funkwhale.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/instagram.svg b/app/Resources/icons/social/instagram.svg deleted file mode 100755 index 7726b140..00000000 --- a/app/Resources/icons/social/instagram.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/linkedin.svg b/app/Resources/icons/social/linkedin.svg deleted file mode 100755 index 9042d1ee..00000000 --- a/app/Resources/icons/social/linkedin.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/mastodon.svg b/app/Resources/icons/social/mastodon.svg deleted file mode 100755 index 7bab6080..00000000 --- a/app/Resources/icons/social/mastodon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/misskey.svg b/app/Resources/icons/social/misskey.svg deleted file mode 100644 index 2b8b3850..00000000 --- a/app/Resources/icons/social/misskey.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/mobilizon.svg b/app/Resources/icons/social/mobilizon.svg deleted file mode 100755 index 564f3ca6..00000000 --- a/app/Resources/icons/social/mobilizon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/peertube.svg b/app/Resources/icons/social/peertube.svg deleted file mode 100755 index 4cf8e7f8..00000000 --- a/app/Resources/icons/social/peertube.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/pixelfed.svg b/app/Resources/icons/social/pixelfed.svg deleted file mode 100755 index cb3e4d66..00000000 --- a/app/Resources/icons/social/pixelfed.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/pleroma.svg b/app/Resources/icons/social/pleroma.svg deleted file mode 100644 index 657fe8ac..00000000 --- a/app/Resources/icons/social/pleroma.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/plume.svg b/app/Resources/icons/social/plume.svg deleted file mode 100755 index 841b3c0e..00000000 --- a/app/Resources/icons/social/plume.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/reddit.svg b/app/Resources/icons/social/reddit.svg deleted file mode 100755 index 8a0d2f72..00000000 --- a/app/Resources/icons/social/reddit.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/slack.svg b/app/Resources/icons/social/slack.svg deleted file mode 100755 index 3737d5a2..00000000 --- a/app/Resources/icons/social/slack.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/tiktok.svg b/app/Resources/icons/social/tiktok.svg deleted file mode 100755 index d6a380d2..00000000 --- a/app/Resources/icons/social/tiktok.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/twitch.svg b/app/Resources/icons/social/twitch.svg deleted file mode 100755 index 79f77875..00000000 --- a/app/Resources/icons/social/twitch.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/twitter.svg b/app/Resources/icons/social/twitter.svg deleted file mode 100755 index 1b07620c..00000000 --- a/app/Resources/icons/social/twitter.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/writefreely.svg b/app/Resources/icons/social/writefreely.svg deleted file mode 100755 index 21bc015a..00000000 --- a/app/Resources/icons/social/writefreely.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/social/youtube.svg b/app/Resources/icons/social/youtube.svg deleted file mode 100755 index 3d875054..00000000 --- a/app/Resources/icons/social/youtube.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Resources/icons/sort.svg b/app/Resources/icons/sort.svg deleted file mode 100644 index 51df3545..00000000 --- a/app/Resources/icons/sort.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/star-smile.svg b/app/Resources/icons/star-smile.svg deleted file mode 100755 index 05014c31..00000000 --- a/app/Resources/icons/star-smile.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/timer.svg b/app/Resources/icons/timer.svg deleted file mode 100755 index 21ab4767..00000000 --- a/app/Resources/icons/timer.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/upload-cloud.svg b/app/Resources/icons/upload-cloud.svg deleted file mode 100755 index b87c7581..00000000 --- a/app/Resources/icons/upload-cloud.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/user-add.svg b/app/Resources/icons/user-add.svg deleted file mode 100755 index 2d56227f..00000000 --- a/app/Resources/icons/user-add.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/user-follow.svg b/app/Resources/icons/user-follow.svg deleted file mode 100644 index e8892f28..00000000 --- a/app/Resources/icons/user-follow.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/Resources/icons/volume-high.svg b/app/Resources/icons/volume-high.svg deleted file mode 100644 index 0aa5be36..00000000 --- a/app/Resources/icons/volume-high.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/volume-low.svg b/app/Resources/icons/volume-low.svg deleted file mode 100644 index 6acfade5..00000000 --- a/app/Resources/icons/volume-low.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/volume-mute.svg b/app/Resources/icons/volume-mute.svg deleted file mode 100644 index 79bd55ac..00000000 --- a/app/Resources/icons/volume-mute.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/icons/warning.svg b/app/Resources/icons/warning.svg deleted file mode 100644 index e01de7fb..00000000 --- a/app/Resources/icons/warning.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/Resources/images/castopod-logo-base.svg b/app/Resources/images/castopod-logo-base.svg deleted file mode 100644 index 482eebfe..00000000 --- a/app/Resources/images/castopod-logo-base.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/Resources/images/castopod-logo.svg b/app/Resources/images/castopod-logo.svg deleted file mode 100644 index 039deb74..00000000 --- a/app/Resources/images/castopod-logo.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/Resources/images/castopod-mascot_confused.svg b/app/Resources/images/castopod-mascot_confused.svg deleted file mode 100644 index ab32c445..00000000 --- a/app/Resources/images/castopod-mascot_confused.svg +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/Resources/js/admin.ts b/app/Resources/js/admin.ts deleted file mode 100644 index 078e919f..00000000 --- a/app/Resources/js/admin.ts +++ /dev/null @@ -1,40 +0,0 @@ -import "@github/markdown-toolbar-element"; -import "@github/time-elements"; -import "./modules/audio-clipper"; -import ClientTimezone from "./modules/ClientTimezone"; -import Clipboard from "./modules/Clipboard"; -import DateTimePicker from "./modules/DateTimePicker"; -import Dropdown from "./modules/Dropdown"; -import HotKeys from "./modules/HotKeys"; -import "./modules/markdown-preview"; -import "./modules/markdown-write-preview"; -import MultiSelect from "./modules/MultiSelect"; -import "./modules/permalink-edit"; -import "./modules/play-soundbite"; -import PublishMessageWarning from "./modules/PublishMessageWarning"; -import Select from "./modules/Select"; -import SidebarToggler from "./modules/SidebarToggler"; -import Slugify from "./modules/Slugify"; -import ThemePicker from "./modules/ThemePicker"; -import Time from "./modules/Time"; -import Tooltip from "./modules/Tooltip"; -import ValidateFileSize from "./modules/ValidateFileSize"; -import "./modules/video-clip-previewer"; -import VideoClipBuilder from "./modules/VideoClipBuilder"; -import "./modules/xml-editor"; - -Dropdown(); -Tooltip(); -Select(); -MultiSelect(); -Slugify(); -SidebarToggler(); -ClientTimezone(); -DateTimePicker(); -Time(); -Clipboard(); -ThemePicker(); -PublishMessageWarning(); -HotKeys(); -ValidateFileSize(); -VideoClipBuilder(); diff --git a/app/Resources/js/app.ts b/app/Resources/js/app.ts deleted file mode 100644 index 7b944f47..00000000 --- a/app/Resources/js/app.ts +++ /dev/null @@ -1,5 +0,0 @@ -import Dropdown from "./modules/Dropdown"; -import Tooltip from "./modules/Tooltip"; - -Dropdown(); -Tooltip(); diff --git a/app/Resources/js/charts.ts b/app/Resources/js/charts.ts deleted file mode 100644 index de52bd13..00000000 --- a/app/Resources/js/charts.ts +++ /dev/null @@ -1,4 +0,0 @@ -import "core-js"; -import DrawCharts from "./modules/Charts"; - -DrawCharts(); diff --git a/app/Resources/js/install.ts b/app/Resources/js/install.ts deleted file mode 100644 index e3bb9d53..00000000 --- a/app/Resources/js/install.ts +++ /dev/null @@ -1,3 +0,0 @@ -import Tooltip from "./modules/Tooltip"; - -Tooltip(); diff --git a/app/Resources/js/map.ts b/app/Resources/js/map.ts deleted file mode 100644 index 66afdef9..00000000 --- a/app/Resources/js/map.ts +++ /dev/null @@ -1,4 +0,0 @@ -import "core-js"; -import DrawEpisodesMaps from "./modules/EpisodesMap"; - -DrawEpisodesMaps(); diff --git a/app/Resources/js/modules/xml-editor.ts b/app/Resources/js/modules/xml-editor.ts deleted file mode 100644 index e32288e3..00000000 --- a/app/Resources/js/modules/xml-editor.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { indentWithTab } from "@codemirror/commands"; -import { xml } from "@codemirror/lang-xml"; -import { - defaultHighlightStyle, - syntaxHighlighting, -} from "@codemirror/language"; -import { Compartment, EditorState } from "@codemirror/state"; -import { keymap } from "@codemirror/view"; -import { basicSetup, EditorView } from "codemirror"; -import { css, html, LitElement, TemplateResult } from "lit"; -import { customElement, queryAssignedNodes, state } from "lit/decorators.js"; -import prettifyXML from "xml-formatter"; - -const language = new Compartment(); - -@customElement("xml-editor") -export class XMLEditor extends LitElement { - @queryAssignedNodes({ slot: "textarea" }) - _textarea!: NodeListOf; - - @state() - editorState!: EditorState; - - @state() - editorView!: EditorView; - - firstUpdated(): void { - const minHeightEditor = EditorView.theme({ - ".cm-content, .cm-gutter": { - minHeight: this._textarea[0].clientHeight + "px", - }, - }); - - let editorContents = ""; - if (this._textarea[0].value) { - try { - editorContents = prettifyXML(this._textarea[0].value, { - indentation: " ", - }); - } catch (e) { - // xml doesn't have a root node - editorContents = prettifyXML( - "" + this._textarea[0].value + "", - { - indentation: " ", - } - ); - // remove root, unnecessary lines and indents - editorContents = editorContents - .replace(/^/, "") - .replace(/<\/root>$/, "") - .replace(/^\s*[\r\n]/gm, "") - .replace(/[\r\n] {2}/gm, "\r\n") - .trim(); - } - } - - this.editorState = EditorState.create({ - doc: editorContents, - extensions: [ - basicSetup, - keymap.of([indentWithTab]), - language.of(xml()), - minHeightEditor, - syntaxHighlighting(defaultHighlightStyle), - ], - }); - - this.editorView = new EditorView({ - state: this.editorState, - root: this.shadowRoot as ShadowRoot, - parent: this.shadowRoot as ShadowRoot, - }); - - this._textarea[0].hidden = true; - if (this._textarea[0].form) { - this._textarea[0].form.addEventListener("submit", () => { - this._textarea[0].value = this.editorView.state.doc.toString(); - }); - } - } - - disconnectedCallback(): void { - if (this._textarea[0].form) { - this._textarea[0].form.removeEventListener("submit", () => { - this._textarea[0].value = this.editorView.state.doc.toString(); - }); - } - } - - static styles = css` - .cm-editor { - border-radius: 0.5rem; - overflow: hidden; - border: 3px solid hsl(var(--color-border-contrast)); - background-color: hsl(var(--color-background-elevated)); - } - .cm-editor.cm-focused { - outline: 2px solid transparent; - box-shadow: 0 0 0 2px hsl(var(--color-background-elevated)), - 0 0 0 calc(4px) hsl(var(--color-accent-base)); - } - .cm-gutters { - background-color: hsl(var(--color-background-elevated)) !important; - } - - .cm-activeLine { - background-color: hsl(var(--color-background-highlight)) !important; - } - - .cm-activeLineGutter { - background-color: hsl(var(--color-background-highlight)) !important; - } - - .ͼ4 .cm-line { - caret-color: hsl(var(--color-text-base)) !important; - } - - .ͼ1 .cm-cursor { - border: none; - } - `; - - render(): TemplateResult<1> { - return html``; - } -} diff --git a/app/Resources/js/podcast.ts b/app/Resources/js/podcast.ts deleted file mode 100644 index e7b0df73..00000000 --- a/app/Resources/js/podcast.ts +++ /dev/null @@ -1,10 +0,0 @@ -import "@github/time-elements"; -import SidebarToggler from "./modules/SidebarToggler"; -import Time from "./modules/Time"; -import Toggler from "./modules/Toggler"; -import Tooltip from "./modules/Tooltip"; - -Time(); -Toggler(); -Tooltip(); -SidebarToggler(); diff --git a/app/Resources/styles/custom.css b/app/Resources/styles/custom.css deleted file mode 100644 index 8cfb46bc..00000000 --- a/app/Resources/styles/custom.css +++ /dev/null @@ -1,59 +0,0 @@ -@layer components { - .post-content { - & a { - @apply text-sm font-semibold text-accent-base hover:text-accent-hover; - } - } - - .ring-accent { - @apply outline-none ring-2 ring-offset-2; - /* FIXME: why doesn't ring-accent-base work? */ - --tw-ring-opacity: 1; - --tw-ring-color: hsl(var(--color-accent-base) / var(--tw-ring-opacity)); - --tw-ring-offset-color: hsl(var(--color-background-base)); - } - - .rounded-conditional-b-xl { - border-bottom-right-radius: max( - 0px, - min(0.75rem, calc((100vw - 0.75rem - 100%) * 9999)) - ); - border-bottom-left-radius: max( - 0px, - min(0.75rem, calc((100vw - 0.75rem - 100%) * 9999)) - ); - } - - .rounded-conditional-2xl { - border-radius: max(0px, min(1rem, calc((100vw - 1rem - 100%) * 9999))); - } - - .rounded-conditional-full { - border-radius: max(0px, min(9999px, calc((100vw - 1rem - 100%) * 9999))); - } - - .backdrop-gradient { - background-image: linear-gradient( - 180deg, - hsla(0 0% 35.29% / 0) 0%, - hsla(0 0% 34.53% / 0.034375) 16.36%, - hsla(0 0% 32.42% / 0.125) 33.34%, - hsla(0 0% 29.18% / 0.253125) 50.1%, - hsla(0 0% 24.96% / 0.4) 65.75%, - hsla(0 0% 19.85% / 0.546875) 79.43%, - hsla(0 0% 13.95% / 0.675) 90.28%, - hsla(0 0% 7.32% / 0.765625) 97.43%, - hsla(0 0% 0% / 0.8) 100% - ); - } - - .bg-stripes-gray { - background-image: repeating-linear-gradient( - -45deg, - #f3f4f6, - #f3f4f6 10px, - #e5e7eb 10px, - #e5e7eb 20px - ); - } -} diff --git a/app/Resources/styles/index.css b/app/Resources/styles/index.css deleted file mode 100644 index 3af00d80..00000000 --- a/app/Resources/styles/index.css +++ /dev/null @@ -1,15 +0,0 @@ -@import "./tailwind.css"; -@import "./custom.css"; -@import "./fonts.css"; -@import "./colors.css"; -@import "./breadcrumb.css"; -@import "./dropdown.css"; -@import "./choices.css"; -@import "./radioBtn.css"; -@import "./colorRadioBtn.css"; -@import "./switch.css"; -@import "./radioToggler.css"; -@import "./formInputTabs.css"; -@import "./stickyHeader.css"; -@import "./readMore.css"; -@import "./seeMore.css"; diff --git a/app/Resources/styles/radioBtn.css b/app/Resources/styles/radioBtn.css deleted file mode 100644 index 5a044cd9..00000000 --- a/app/Resources/styles/radioBtn.css +++ /dev/null @@ -1,22 +0,0 @@ -@layer components { - .form-radio-btn { - @apply absolute mt-3 ml-3 border-contrast border-3 text-accent-base; - - &:focus { - @apply ring-accent; - } - - &:checked { - @apply ring-2 ring-contrast; - - & + label { - @apply text-accent-contrast bg-accent-base; - } - } - - & + label { - @apply inline-flex items-center py-2 pl-8 pr-2 text-sm font-semibold rounded-lg cursor-pointer border-contrast bg-elevated border-3; - color: hsl(var(--color-text-muted)); - } - } -} diff --git a/app/Validation/FileRules.php b/app/Validation/FileRules.php index 49f5fb38..2a149d46 100644 --- a/app/Validation/FileRules.php +++ b/app/Validation/FileRules.php @@ -11,13 +11,15 @@ declare(strict_types=1); namespace App\Validation; use CodeIgniter\Validation\FileRules as ValidationFileRules; +use Override; class FileRules extends ValidationFileRules { /** * Checks an uploaded file to verify that the dimensions are within a specified allowable dimension. */ - public function min_dims(string $blank = null, string $params = ''): bool + #[Override] + public function min_dims(?string $blank = null, string $params = ''): bool { // Grab the file name off the top of the $params // after we split it. @@ -59,7 +61,7 @@ class FileRules extends ValidationFileRules /** * Checks an uploaded image to verify that the ratio corresponds to the params */ - public function is_image_ratio(string $blank = null, string $params = ''): bool + public function is_image_ratio(?string $blank = null, string $params = ''): bool { // Grab the file name off the top of the $params // after we split it. @@ -95,4 +97,43 @@ class FileRules extends ValidationFileRules } //-------------------------------------------------------------------- + + /** + * Checks that an uploaded json file's content is valid + */ + public function is_json(?string $blank = null, string $params = ''): bool + { + // Grab the file name off the top of the $params + // after we split it. + $params = explode(',', $params); + $name = array_shift($params); + + if (! ($files = $this->request->getFileMultiple($name))) { + $files = [$this->request->getFile($name)]; + } + + foreach ($files as $file) { + if ($file === null) { + return false; + } + + if ($file->getError() === UPLOAD_ERR_NO_FILE) { + return true; + } + + $content = file_get_contents($file->getTempName()); + + if ($content === false) { + return false; + } + + json_decode($content); + + if (json_last_error() !== JSON_ERROR_NONE) { + return false; + } + } + + return true; + } } diff --git a/app/Validation/OtherRules.php b/app/Validation/OtherRules.php new file mode 100644 index 00000000..74782809 --- /dev/null +++ b/app/Validation/OtherRules.php @@ -0,0 +1,29 @@ + 'alert', + ]; /** * @var 'default'|'success'|'danger'|'warning' */ protected string $variant = 'default'; + #[Override] public function render(): string { - $variants = [ + $variantData = match ($this->variant) { 'success' => [ 'class' => 'text-pine-900 bg-pine-100 border-pine-300', - 'glyph' => 'check', + 'glyph' => 'check-fill', // @icon("check-fill") ], 'danger' => [ 'class' => 'text-red-900 bg-red-100 border-red-300', - 'glyph' => 'close', + 'glyph' => 'close-fill', // @icon("close-fill") ], 'warning' => [ 'class' => 'text-yellow-900 bg-yellow-100 border-yellow-300', - 'glyph' => 'alert', + 'glyph' => 'alert-fill', // @icon("alert-fill") ], - ]; + default => [ + 'class' => 'text-blue-900 bg-blue-100 border-blue-300', + 'glyph' => 'error-warning-fill', // @icon("error-warning-fill") + ], + }; - $glyph = ''; - $title = $this->title === null ? '' : '
' . $this->title . '
'; - $class = 'inline-flex w-full p-2 text-sm border rounded ' . $variants[$this->variant]['class'] . ' ' . $this->class; - - unset($this->attributes['slot']); - unset($this->attributes['variant']); - $attributes = stringify_attributes($this->attributes); + $glyph = icon(($this->glyph === '' ? $variantData['glyph'] : $this->glyph), [ + 'class' => 'flex-shrink-0 mr-2 text-lg', + ]); + $title = $this->title === '' ? '' : '
' . $this->title . '
'; + $this->mergeClass('inline-flex w-full p-2 text-sm border rounded '); + $this->mergeClass($variantData['class']); return <<{$glyph}
{$title}

{$this->slot}

+
getStringifiedAttributes()}>{$glyph}
{$title}

{$this->slot}

HTML; } } diff --git a/app/Views/Components/Button.php b/app/Views/Components/Button.php index 292adf48..90d2ca63 100644 --- a/app/Views/Components/Button.php +++ b/app/Views/Components/Button.php @@ -4,14 +4,25 @@ declare(strict_types=1); namespace App\Views\Components; +use Override; use ViewComponents\Component; class Button extends Component { + protected array $props = ['uri', 'variant', 'size', 'iconLeft', 'iconRight', 'isSquared', 'isExternal']; + + protected array $casts = [ + 'isSquared' => 'boolean', + 'isExternal' => 'boolean', + ]; + protected string $uri = ''; protected string $variant = 'default'; + /** + * @var 'small'|'base'|'large' + */ protected string $size = 'base'; protected string $iconLeft = ''; @@ -20,106 +31,86 @@ class Button extends Component protected bool $isSquared = false; - public function setIsSquared(string $value): void - { - $this->isSquared = $value === 'true'; - } + protected bool $isExternal = false; + #[Override] public function render(): string { - $baseClass = - 'gap-x-2 flex-shrink-0 inline-flex items-center justify-center font-semibold shadow-xs rounded-full focus:ring-accent'; + $this->mergeClass('shadow gap-x-2 flex-shrink-0 inline-flex items-center justify-center font-semibold rounded-full'); - $variantClass = [ - 'default' => 'text-black bg-gray-300 hover:bg-gray-400', - 'primary' => 'text-accent-contrast bg-accent-base hover:bg-accent-hover', - 'secondary' => 'border-2 border-accent-base text-accent-base bg-white hover:border-accent-hover hover:text-accent-hover', - 'success' => 'text-white bg-pine-500 hover:bg-pine-800', - 'danger' => 'text-white bg-red-600 hover:bg-red-700', - 'warning' => 'text-black bg-yellow-500 hover:bg-yellow-600', - 'info' => 'text-white bg-blue-500 hover:bg-blue-600', - 'disabled' => 'text-black bg-gray-300 cursor-not-allowed', - ]; + $variantClass = match ($this->variant) { + 'primary' => 'text-accent-contrast bg-accent-base hover:bg-accent-hover', + 'secondary' => 'ring-2 ring-accent-base ring-inset text-accent-base bg-white hover:border-accent-hover hover:text-accent-hover hover:ring-accent-hover', + 'danger' => 'bg-red-50 ring-2 ring-red-700 ring-inset text-red-700 hover:ring-red-800 hover:text-red-800', + 'warning' => 'bg-yellow-50 ring-2 ring-yellow-700 ring-inset text-yellow-700 hover:ring-yellow-800 hover:text-yellow-800', + 'info' => 'bg-blue-50 ring-2 ring-blue-700 ring-inset text-blue-700 hover:ring-blue-800 hover:text-blue-800', + 'disabled' => 'text-black bg-gray-300 cursor-not-allowed', + default => 'text-black bg-gray-50 hover:bg-gray-200', + }; - $sizeClass = [ + $sizeClass = match ($this->size) { 'small' => 'text-xs leading-6', - 'base' => 'text-sm leading-5', 'large' => 'text-base leading-6', - ]; + default => 'text-sm leading-5', + }; - $iconSize = [ + $iconSizeClass = match ($this->size) { 'small' => 'text-sm', - 'base' => 'text-lg', 'large' => 'text-2xl', - ]; + default => 'text-lg', + }; - $basePaddings = [ + $basePaddings = match ($this->size) { 'small' => 'px-3 py-1', - 'base' => 'px-3 py-2', 'large' => 'px-4 py-2', - ]; + default => 'px-3 py-2', + }; - $squaredPaddings = [ + $squaredPaddings = match ($this->size) { 'small' => 'p-1', - 'base' => 'p-2', 'large' => 'p-3', - ]; + default => 'p-2', + }; - $buttonClass = - $baseClass . - ' ' . - ($this->isSquared - ? $squaredPaddings[$this->size] - : $basePaddings[$this->size]) . - ' ' . - $sizeClass[$this->size] . - ' ' . - $variantClass[$this->variant]; + $this->mergeClass($variantClass); + $this->mergeClass($sizeClass); - if (array_key_exists('class', $this->attributes)) { - $buttonClass .= ' ' . $this->attributes['class']; - unset($this->attributes['class']); + if ($this->isSquared) { + $this->mergeClass($squaredPaddings); + } else { + $this->mergeClass($basePaddings); + } + + if ($this->iconLeft !== '' || $this->iconRight !== '') { + $this->slot = '' . $this->slot . ''; } if ($this->iconLeft !== '') { - $this->slot = (new Icon([ - 'glyph' => $this->iconLeft, - 'class' => 'opacity-75' . ' ' . $iconSize[$this->size], - ]))->render() . $this->slot; + $this->slot = icon($this->iconLeft, [ + 'class' => 'opacity-75 ' . $iconSizeClass, + ]) . $this->slot; } if ($this->iconRight !== '') { - $this->slot .= (new Icon([ - 'glyph' => $this->iconRight, - 'class' => 'opacity-75' . ' ' . $iconSize[$this->size], - ]))->render(); + $this->slot .= icon($this->iconRight, [ + 'class' => 'opacity-75 ' . $iconSizeClass, + ]); } - unset($this->attributes['slot']); - unset($this->attributes['variant']); - unset($this->attributes['size']); - unset($this->attributes['iconLeft']); - unset($this->attributes['iconRight']); - unset($this->attributes['isSquared']); - unset($this->attributes['uri']); - unset($this->attributes['label']); - if ($this->uri !== '') { $tagName = 'a'; - $defaultButtonAttributes = [ - 'href' => $this->uri, - ]; + $this->attributes['href'] = $this->uri; + if ($this->isExternal) { + $this->attributes['target'] = '_blank'; + $this->attributes['rel'] = 'noopener noreferrer'; + } } else { $tagName = 'button'; - $defaultButtonAttributes = [ - 'type' => 'button', - ]; + $this->attributes['type'] ??= 'button'; } - $attributes = stringify_attributes(array_merge($defaultButtonAttributes, $this->attributes)); - return <<{$this->slot} + <{$tagName} {$this->getStringifiedAttributes()}>{$this->slot} HTML; } } diff --git a/app/Views/Components/Charts/ChartsComponent.php b/app/Views/Components/Charts/ChartsComponent.php index 8dbd5bfc..7d680f5a 100644 --- a/app/Views/Components/Charts/ChartsComponent.php +++ b/app/Views/Components/Charts/ChartsComponent.php @@ -4,21 +4,35 @@ declare(strict_types=1); namespace App\Views\Components\Charts; +use Override; use ViewComponents\Component; class ChartsComponent extends Component { - protected string $title = ''; + protected array $props = ['title', 'subtitle', 'dataUrl', 'type']; - protected string $dataUrl = ''; + protected string $title; - protected string $type = ''; + protected string $subtitle = ''; + protected string $dataUrl; + + protected string $type; + + #[Override] public function render(): string { + $subtitleBlock = ''; + if ($this->subtitle !== '') { + $subtitleBlock = '

' . $this->subtitle . '

'; + } + + $this->mergeClass('bg-elevated border-3 rounded-xl border-subtle'); + return << +
getStringifiedAttributes()}>

{$this->title}

+ {$subtitleBlock}
HTML; diff --git a/app/Views/Components/Charts/XYDuration.php b/app/Views/Components/Charts/XYDuration.php new file mode 100644 index 00000000..bd881c56 --- /dev/null +++ b/app/Views/Components/Charts/XYDuration.php @@ -0,0 +1,10 @@ +subtitle = html_entity_decode($value); } + #[Override] public function render(): string { - $glyph = icon($this->glyph, 'flex-shrink-0 bg-base rounded-full w-8 h-8 p-2 text-accent-base'); + $glyph = (string) icon($this->glyph, [ + 'class' => 'flex-shrink-0 bg-base rounded-full w-8 h-8 p-2 text-accent-base', + ]); - if ($this->href !== null && $this->href !== '') { - $chevronRight = icon('chevron-right'); + if ($this->href !== '') { + $chevronRight = icon('arrow-right-s-fill'); $viewLang = lang('Common.view'); return << +
{$glyph}
{$this->title}
{$viewLang}{$chevronRight}

{$this->subtitle}

-
{$this->slot}
+
{$this->slot}
HTML; } return << +
{$glyph}
{$this->title}

{$this->subtitle}

-
{$this->slot}
+
{$this->slot}
HTML; } diff --git a/app/Views/Components/DropdownMenu.php b/app/Views/Components/DropdownMenu.php index bed2220f..992ef22e 100644 --- a/app/Views/Components/DropdownMenu.php +++ b/app/Views/Components/DropdownMenu.php @@ -5,27 +5,37 @@ declare(strict_types=1); namespace App\Views\Components; use Exception; +use Override; use ViewComponents\Component; class DropdownMenu extends Component { - public string $id = ''; + protected array $props = ['id', 'labelledby', 'placement', 'offsetX', 'offsetY', 'items']; - public string $labelledby; + protected array $casts = [ + 'offsetX' => 'number', + 'offsetY' => 'number', + 'items' => 'array', + ]; - public string $placement = 'bottom-end'; + protected string $id; - public string $offsetX = '0'; + protected string $labelledby; - public string $offsetY = '0'; + protected string $placement = 'bottom-end'; - public array $items = []; + protected int $offsetX = 0; + + protected int $offsetY = 0; + + protected array $items = []; public function setItems(string $value): void { $this->items = json_decode(htmlspecialchars_decode($value), true); } + #[Override] public function render(): string { if ($this->items === []) { @@ -37,11 +47,11 @@ class DropdownMenu extends Component switch ($item['type']) { case 'link': $menuItems .= anchor($item['uri'], $item['title'], [ - 'class' => 'px-4 py-1 hover:bg-highlight focus:ring-accent focus:ring-inset' . (array_key_exists('class', $item) ? ' ' . $item['class'] : ''), + 'class' => 'inline-flex gap-x-1 items-center px-4 py-1 hover:bg-highlight' . (array_key_exists('class', $item) ? ' ' . $item['class'] : ''), ]); break; case 'html': - $menuItems .= htmlspecialchars_decode($item['content']); + $menuItems .= htmlspecialchars_decode((string) $item['content']); break; case 'separator': $menuItems .= '
'; @@ -51,14 +61,16 @@ class DropdownMenu extends Component } } + $this->mergeClass('absolute flex flex-col py-2 rounded-lg z-60 whitespace-nowrap text-skin-base border-contrast bg-elevated border-3'); + $this->attributes['id'] = $this->id; + $this->attributes['aria-labelledby'] = $this->labelledby; + $this->attributes['data-dropdown'] = 'menu'; + $this->attributes['data-dropdown-placement'] = $this->placement; + $this->attributes['data-dropdown-offset-x'] = $this->offsetX; + $this->attributes['data-dropdown-offset-y'] = $this->offsetY; + return <<{$menuItems} + HTML; } } diff --git a/app/Views/Components/Forms/Checkbox.php b/app/Views/Components/Forms/Checkbox.php index 63f033f7..08c94bc5 100644 --- a/app/Views/Components/Forms/Checkbox.php +++ b/app/Views/Components/Forms/Checkbox.php @@ -4,37 +4,59 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use App\Views\Components\Hint; +use Override; + class Checkbox extends FormComponent { - protected ?string $hint = null; + protected array $props = ['hint', 'helper']; - protected bool $isChecked = false; + protected array $casts = [ + 'isChecked' => 'boolean', + ]; - public function setIsChecked(string $value): void - { - $this->isChecked = $value === 'true'; - } + protected string $hint = ''; + protected string $helper = ''; + + #[Override] public function render(): string { - $attributes = [ - 'id' => $this->value, - 'name' => $this->name, - 'class' => 'form-checkbox bg-elevated text-accent-base border-contrast border-3 focus:ring-accent w-6 h-6', - ]; - if ($this->required) { - $attributes['required'] = 'required'; - } $checkboxInput = form_checkbox( - $attributes, + [ + 'id' => $this->id, + 'name' => $this->name, + 'class' => 'form-checkbox bg-elevated text-accent-base border-contrast border-3 focus:ring-accent w-6 h-6 transition', + ], 'yes', - old($this->name) ? old($this->name) === $this->value : $this->isChecked, + in_array($this->getValue(), ['yes', 'true', 'on', '1'], true), ); - $hint = $this->hint === null ? '' : hint_tooltip($this->hint, 'ml-1'); + $hint = $this->hint === '' ? '' : new Hint([ + 'class' => 'ml-1', + 'slot' => $this->hint, + ])->render(); + + $this->mergeClass('inline-flex items-start gap-x-2'); + + $helperText = ''; + if ($this->helper !== '') { + $helperId = $this->name . 'Help'; + $helperText = new Helper([ + 'id' => $helperId, + 'slot' => $this->helper, + 'class' => '-mt-1', + ])->render(); + $this->attributes['aria-describedby'] = $helperId; + } return <<{$checkboxInput}{$this->slot}{$hint} + HTML; } } diff --git a/app/Views/Components/Forms/CodeEditor.php b/app/Views/Components/Forms/CodeEditor.php new file mode 100644 index 00000000..ab20988f --- /dev/null +++ b/app/Views/Components/Forms/CodeEditor.php @@ -0,0 +1,35 @@ + '6', + 'class' => 'bg-elevated w-full rounded-lg border-3 border-contrast focus:border-contrast focus-within:ring-accent transition', + ]; + + protected string $lang = ''; + + public function setValue(string $value): void + { + $this->value = htmlspecialchars_decode($value); + } + + #[Override] + public function render(): string + { + $this->attributes['slot'] = 'textarea'; + $textarea = form_textarea($this->attributes, $this->getValue()); + + return <<{$textarea} + HTML; + } +} diff --git a/app/Views/Components/Forms/ColorRadioButton.php b/app/Views/Components/Forms/ColorRadioButton.php index 470200ef..f1ff3fde 100644 --- a/app/Views/Components/Forms/ColorRadioButton.php +++ b/app/Views/Components/Forms/ColorRadioButton.php @@ -4,37 +4,39 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; + class ColorRadioButton extends FormComponent { - protected bool $isChecked = false; + protected array $props = ['isSelected']; - protected string $style = ''; + protected array $casts = [ + 'isSelected' => 'boolean', + ]; - public function setIsChecked(string $value): void - { - $this->isChecked = $value === 'true'; - } + protected bool $isSelected = false; + #[Override] public function render(): string { $data = [ - 'id' => $this->value, - 'name' => $this->name, + 'id' => $this->value, + 'name' => $this->name, 'class' => 'color-radio-btn', ]; - if ($this->required) { + if ($this->isRequired) { $data['required'] = 'required'; } $radioInput = form_radio( $data, $this->value, - old($this->name) ? old($this->name) === $this->value : $this->isChecked, + old($this->name) ? old($this->name) === $this->value : $this->isSelected, ); return << +
getStringifiedAttributes()}> {$radioInput}
diff --git a/app/Views/Components/Forms/DatetimePicker.php b/app/Views/Components/Forms/DatetimePicker.php index 1e3de877..1267ffef 100644 --- a/app/Views/Components/Forms/DatetimePicker.php +++ b/app/Views/Components/Forms/DatetimePicker.php @@ -4,23 +4,34 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; + class DatetimePicker extends FormComponent { + protected array $attributes = [ + 'data-picker' => 'datetime', + ]; + + #[Override] public function render(): string { - $this->attributes['class'] = 'rounded-l-lg border-0 border-rounded-r-none flex-1 focus:ring-0'; - $this->attributes['data-input'] = ''; - $dateInput = form_input($this->attributes, old($this->name, $this->value)); + $dateInput = form_input([ + 'name' => $this->name, + 'class' => 'rounded-l-lg border-0 border-rounded-r-none flex-1 focus:ring-0', + 'data-input' => '', + ], $this->getValue()); $clearLabel = lang( 'Episode.publish_form.scheduled_publication_date_clear', ); - $closeIcon = icon('close'); + $closeIcon = icon('close-fill'); + + $this->mergeClass('flex border-3 rounded-lg border-contrast focus-within:ring-accent transition'); return << +
getStringifiedAttributes()}> {$dateInput} -
diff --git a/app/Views/Components/Forms/Field.php b/app/Views/Components/Forms/Field.php index 8467df64..0efc0d09 100644 --- a/app/Views/Components/Forms/Field.php +++ b/app/Views/Components/Forms/Field.php @@ -4,52 +4,82 @@ declare(strict_types=1); namespace App\Views\Components\Forms; -class Field extends FormComponent +use Override; +use ViewComponents\Component; + +class Field extends Component { + protected array $props = [ + 'name', + 'label', + 'isRequired', + 'isReadonly', + 'as', + 'hint', + 'helper', + ]; + + protected array $casts = [ + 'isRequired' => 'boolean', + 'isReadonly' => 'boolean', + ]; + + protected string $name; + + protected string $label; + + protected bool $isRequired = false; + + protected bool $isReadonly = false; + protected string $as = 'Input'; - protected string $label = ''; + protected string $hint = ''; - protected ?string $helper = null; - - protected ?string $hint = null; + protected string $helper = ''; + #[Override] public function render(): string { $helperText = ''; - if ($this->helper !== null) { - $helperId = $this->id . 'Help'; - $helperText = '' . $this->helper . ''; + if ($this->helper !== '') { + $helperId = $this->name . 'Help'; + $helperText = new Helper([ + 'id' => $helperId, + 'slot' => $this->helper, + ])->render(); $this->attributes['aria-describedby'] = $helperId; } $labelAttributes = [ - 'for' => $this->id, - 'isOptional' => $this->required ? 'false' : 'true', + 'for' => $this->name, + 'isOptional' => $this->isRequired ? 'false' : 'true', + 'class' => '-mb-1', + 'slot' => $this->label, ]; - if ($this->hint) { + if ($this->hint !== '') { $labelAttributes['hint'] = $this->hint; } - $labelAttributes = stringify_attributes($labelAttributes); + $label = new Label($labelAttributes); - // remove field specific attributes to inject the rest to Form Component - $fieldComponentAttributes = $this->attributes; - unset($fieldComponentAttributes['as']); - unset($fieldComponentAttributes['label']); - unset($fieldComponentAttributes['class']); - unset($fieldComponentAttributes['helper']); - unset($fieldComponentAttributes['hint']); + $this->mergeClass('flex flex-col'); + $fieldClass = $this->attributes['class']; - $fieldComponentAttributes['class'] = 'mb-1'; + unset($this->attributes['class']); + $this->attributes['name'] = $this->name; + $this->attributes['isRequired'] = var_export($this->isRequired, true); + $this->attributes['isReadonly'] = var_export($this->isReadonly, true); $element = __NAMESPACE__ . '\\' . $this->as; - $fieldElement = new $element($fieldComponentAttributes); + $fieldElement = new $element($this->attributes); return << - {$this->label} - {$fieldElement->render()} +
+ {$label->render()} {$helperText} +
+ {$fieldElement->render()} +
HTML; } diff --git a/app/Views/Components/Forms/FormComponent.php b/app/Views/Components/Forms/FormComponent.php index d8883e04..bcc46b26 100644 --- a/app/Views/Components/Forms/FormComponent.php +++ b/app/Views/Components/Forms/FormComponent.php @@ -6,48 +6,77 @@ namespace App\Views\Components\Forms; use ViewComponents\Component; -class FormComponent extends Component +abstract class FormComponent extends Component { - protected ?string $id = null; + protected array $props = [ + 'id', + 'name', + 'value', + 'defaultValue', + 'isRequired', + 'isReadonly', + ]; - protected string $name = ''; + protected array $casts = [ + 'isRequired' => 'boolean', + 'isReadonly' => 'boolean', + ]; - protected string $value = ''; + protected string $id; - protected bool $required = false; + protected string $name; - protected bool $readonly = false; + /** + * @var string|string[]|null + */ + protected string|array|null $value = null; + + /** + * @var string|string[]|null + */ + protected string|array|null $defaultValue = null; + + protected bool $isRequired = false; + + protected bool $isReadonly = false; /** * @param array $attributes */ public function __construct(array $attributes) { + $parentVars = get_class_vars(self::class); + $this->casts = [...$parentVars['casts'], ...$this->casts]; + $this->props = [...$parentVars['props'], $this->props]; + parent::__construct($attributes); - if ($this->id === null) { + if (! isset($this->id)) { $this->id = $this->name; - $this->attributes['id'] = $this->id; } - } - public function setValue(string $value): void - { - $this->value = htmlspecialchars_decode($value, ENT_QUOTES); - } + $this->attributes['id'] = $this->id; + $this->attributes['name'] = $this->name; - public function setRequired(string $value): void - { - $this->required = $value === 'true'; - } + if ($this->isRequired) { + $this->attributes['required'] = 'required'; + } - public function setReadonly(string $value): void - { - $this->readonly = $value === 'true'; - if ($this->readonly) { + if ($this->isReadonly) { $this->attributes['readonly'] = 'readonly'; - } else { - unset($this->attributes['readonly']); } } + + protected function getValue(): string|array + { + $valueCast = $this->casts['value'] ?? ''; + if ($valueCast === 'array') { + return old($this->name, in_array($this->value, [[], null], true) ? $this->defaultValue : $this->value) ?? []; + } + + return old( + $this->name, + in_array($this->value, ['', null], true) ? $this->defaultValue : $this->value, + ) ?? ''; + } } diff --git a/app/Views/Components/Forms/Helper.php b/app/Views/Components/Forms/Helper.php index f1702573..fbc895d1 100644 --- a/app/Views/Components/Forms/Helper.php +++ b/app/Views/Components/Forms/Helper.php @@ -4,19 +4,20 @@ declare(strict_types=1); namespace App\Views\Components\Forms; -class Helper extends FormComponent -{ - /** - * @var 'default'|'error' - */ - protected string $type = 'default'; +use Override; +use ViewComponents\Component; +class Helper extends Component +{ + // TODO: add type with error and show errors inline + + #[Override] public function render(): string { - $class = 'text-skin-muted'; + $this->mergeClass('form-helper'); return <<{$this->slot} + getStringifiedAttributes()}>{$this->slot} HTML; } } diff --git a/app/Views/Components/Forms/Input.php b/app/Views/Components/Forms/Input.php index 4a705ed2..a45d3224 100644 --- a/app/Views/Components/Forms/Input.php +++ b/app/Views/Components/Forms/Input.php @@ -4,28 +4,33 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; + class Input extends FormComponent { + protected array $props = ['type']; + protected string $type = 'text'; + #[Override] public function render(): string { - $baseClass = 'w-full bg-elevated border-contrast rounded-lg focus:border-contrast border-3 focus:ring-accent focus-within:ring-accent ' . $this->class; - - $this->attributes['class'] = $baseClass; + $this->mergeClass('w-full border-contrast rounded-lg focus:border-contrast border-3 focus-within:ring-accent transition'); if ($this->type === 'file') { - $this->attributes['class'] .= ' file:px-3 file:py-2 file:h-[40px] file:font-semibold file:text-skin-muted file:text-sm file:rounded-none file:border-none file:bg-highlight file:cursor-pointer'; + $this->mergeClass('file:px-3 file:py-2 file:h-[40px] file:font-semibold file:text-accent-hover file:text-sm file:rounded-none file:border-none file:bg-base file:cursor-pointer'); } else { - $this->attributes['class'] .= ' px-3 py-2'; + $this->mergeClass('px-3 py-2'); } - unset($this->attributes['required']); - - if ($this->required) { - $this->attributes['required'] = 'required'; + if ($this->isReadonly) { + $this->mergeClass('bg-base'); + } else { + $this->mergeClass('bg-elevated'); } - return form_input($this->attributes, old($this->name, $this->value)); + $this->attributes['type'] = $this->type; + + return form_input($this->attributes, $this->getValue()); } } diff --git a/app/Views/Components/Forms/Label.php b/app/Views/Components/Forms/Label.php index 49bbac3e..3f8af6f6 100644 --- a/app/Views/Components/Forms/Label.php +++ b/app/Views/Components/Forms/Label.php @@ -4,39 +4,42 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use App\Views\Components\Hint; +use Override; use ViewComponents\Component; class Label extends Component { - protected ?string $for = null; + protected array $props = ['for', 'hint', 'isOptional']; - protected ?string $hint = null; + protected array $casts = [ + 'isOptional' => 'boolean', + ]; + + protected string $for; + + protected string $hint = ''; protected bool $isOptional = false; - public function setIsOptional(string $value): void - { - $this->isOptional = $value === 'true'; - } - + #[Override] public function render(): string { - $labelClass = 'text-sm ' . $this->attributes['class']; - unset($this->attributes['class']); + $this->mergeClass('text-sm font-semibold'); - $optionalText = $this->isOptional ? '(' . + $optionalText = $this->isOptional ? '(' . lang('Common.optional') . ')' : ''; - $hint = $this->hint === null ? '' : hint_tooltip($this->hint, 'ml-1'); - unset($this->attributes['isOptional']); - unset($this->attributes['hint']); - unset($this->attributes['slot']); + $hint = $this->hint === '' ? '' : new Hint([ + 'class' => 'ml-1', + 'slot' => $this->hint, + ])->render(); - $attributes = stringify_attributes($this->attributes); + $this->attributes['for'] = $this->for; return <<{$this->slot}{$optionalText}{$hint} + HTML; } } diff --git a/app/Views/Components/Forms/MarkdownEditor.php b/app/Views/Components/Forms/MarkdownEditor.php index 6625050d..0549fd05 100644 --- a/app/Views/Components/Forms/MarkdownEditor.php +++ b/app/Views/Components/Forms/MarkdownEditor.php @@ -4,8 +4,12 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; + class MarkdownEditor extends FormComponent { + protected array $props = ['disallowList']; + /** * @var string[] */ @@ -16,69 +20,69 @@ class MarkdownEditor extends FormComponent $this->disallowList = explode(',', $value); } + #[Override] public function render(): string { - $editorClass = 'w-full flex flex-col bg-elevated border-3 border-contrast rounded-lg overflow-hidden focus-within:ring-accent ' . $this->class; + $this->mergeClass('w-full flex flex-col bg-elevated border-3 border-contrast rounded-lg overflow-hidden focus-within:ring-accent transition'); + $wrapperClass = $this->attributes['class']; $this->attributes['class'] = 'bg-elevated border-none focus:border-none focus:outline-none focus:ring-0 w-full h-full'; $this->attributes['rows'] = 6; - $textarea = form_textarea($this->attributes, old($this->name, $this->value)); - $markdownIcon = icon( - 'markdown', - 'mr-1 text-lg opacity-40' + $textarea = form_textarea( + $this->attributes, + $this->getValue(), ); + $markdownIcon = (string) icon('markdown-fill', [ + 'class' => 'mr-1 text-lg opacity-40', + ]); + $translations = [ - 'write' => lang('Common.forms.editor.write'), + 'write' => lang('Common.forms.editor.write'), 'preview' => lang('Common.forms.editor.preview'), - 'help' => lang('Common.forms.editor.help'), + 'help' => lang('Common.forms.editor.help'), ]; $toolbarGroups = [ [ [ 'name' => 'header', - 'tag' => 'md-header', - 'icon' => icon('heading'), + 'tag' => 'md-header', + 'icon' => (string) icon('heading'), ], [ 'name' => 'bold', - 'tag' => 'md-bold', - 'icon' => icon('bold'), + 'tag' => 'md-bold', + 'icon' => (string) icon('bold'), ], [ 'name' => 'italic', - 'tag' => 'md-italic', - 'icon' => icon('italic'), + 'tag' => 'md-italic', + 'icon' => (string) icon('italic'), ], ], [ [ 'name' => 'unordered-list', - 'tag' => 'md-unordered-list', - 'icon' => icon('list-unordered'), + 'tag' => 'md-unordered-list', + 'icon' => (string) icon('list-unordered'), ], [ 'name' => 'ordered-list', - 'tag' => 'md-ordered-list ', - 'icon' => icon('list-ordered'), + 'tag' => 'md-ordered-list ', + 'icon' => (string) icon('list-ordered-2'), ], ], [ - [ - 'name' => 'quote', - 'tag' => 'md-quote', - 'icon' => icon('quote'), - ], [ 'name' => 'link', - 'tag' => 'md-link', - 'icon' => icon('link'), + 'tag' => 'md-link', + 'icon' => (string) icon('link'), ], [ 'name' => 'image', - 'tag' => 'md-image', - 'icon' => icon('image-add'), + 'tag' => 'md-image', + 'icon' => (string) icon('image-add-fill'), ], ], ]; @@ -88,19 +92,19 @@ class MarkdownEditor extends FormComponent $toolbarContent .= '
'; foreach ($buttonsGroup as $button) { if (! in_array($button['name'], $this->disallowList, true)) { - $toolbarContent .= '<' . $button['tag'] . ' class="opacity-50 hover:opacity-100 focus:ring-accent focus:opacity-100">' . $button['icon'] . ''; + $toolbarContent .= '<' . $button['tag'] . ' class="opacity-50 hover:opacity-100 focus:opacity-100">' . $button['icon'] . ''; } } $toolbarContent .= '
'; } return << +
- - + + {$toolbarContent}
diff --git a/app/Views/Components/Forms/MultiSelect.php b/app/Views/Components/Forms/MultiSelect.php deleted file mode 100644 index 67d0efd2..00000000 --- a/app/Views/Components/Forms/MultiSelect.php +++ /dev/null @@ -1,45 +0,0 @@ - - */ - protected array $options = []; - - /** - * @var string[] - */ - protected array $selected = []; - - public function setOptions(string $value): void - { - $this->options = json_decode(htmlspecialchars_decode($value), true); - } - - public function setSelected(string $selected): void - { - $this->selected = json_decode(htmlspecialchars_decode($selected), true); - } - - public function render(): string - { - $defaultAttributes = [ - 'data-class' => $this->attributes['class'], - 'multiple' => 'multiple', - 'data-select-text' => lang('Common.forms.multiSelect.selectText'), - 'data-loading-text' => lang('Common.forms.multiSelect.loadingText'), - 'data-no-results-text' => lang('Common.forms.multiSelect.noResultsText'), - 'data-no-choices-text' => lang('Common.forms.multiSelect.noChoicesText'), - 'data-max-item-text' => lang('Common.forms.multiSelect.maxItemText'), - ]; - $this->attributes['class'] .= ' bg-elevated border-3 border-contrast rounded-lg'; - $extra = array_merge($defaultAttributes, $this->attributes); - - return form_dropdown($this->name, $this->options, $this->selected, $extra); - } -} diff --git a/app/Views/Components/Forms/PermalinkEditor.php b/app/Views/Components/Forms/PermalinkEditor.php new file mode 100644 index 00000000..26cf31c9 --- /dev/null +++ b/app/Views/Components/Forms/PermalinkEditor.php @@ -0,0 +1,41 @@ +mergeClass('flex-1 text-xs border-contrast rounded-lg focus:border-contrast border-3 focus-within:ring-accent transition'); + + $this->attributes['slot'] = 'slug-input'; + $input = form_input($this->attributes, $this->getValue()); + + $editLabel = lang('Common.edit'); + $copyLabel = lang('Common.copy'); + $copiedLabel = lang('Common.copied'); + + return << + {$this->label} + + {$this->prefix} + {$input} + +
+ HTML; + } +} diff --git a/app/Views/Components/Forms/Radio.php b/app/Views/Components/Forms/Radio.php index 9a67dd2e..beac8525 100644 --- a/app/Views/Components/Forms/Radio.php +++ b/app/Views/Components/Forms/Radio.php @@ -4,29 +4,35 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; + class Radio extends FormComponent { + protected array $props = ['isChecked']; + + protected array $casts = [ + 'isChecked' => 'boolean', + ]; + protected bool $isChecked = false; - public function setIsChecked(string $value): void - { - $this->isChecked = $value === 'true'; - } - + #[Override] public function render(): string { $radioInput = form_radio( [ - 'id' => $this->value, - 'name' => $this->name, - 'class' => 'text-accent-base bg-elevated border-contrast border-3 focus:ring-accent w-6 h-6', + 'id' => $this->value, + 'name' => $this->name, + 'class' => 'text-accent-base bg-elevated border-contrast border-3 focus:ring-accent w-6 h-6 transition', ], - $this->value, + $this->getValue(), old($this->name) ? old($this->name) === $this->value : $this->isChecked, ); + $this->mergeClass('inline-flex items-center'); + return <<{$radioInput}{$this->slot} + HTML; } } diff --git a/app/Views/Components/Forms/RadioButton.php b/app/Views/Components/Forms/RadioButton.php index 1772c3d4..9f470b33 100644 --- a/app/Views/Components/Forms/RadioButton.php +++ b/app/Views/Components/Forms/RadioButton.php @@ -4,41 +4,57 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; + class RadioButton extends FormComponent { - protected bool $isChecked = false; + protected array $props = ['isSelected', 'description']; - protected ?string $hint = null; + protected array $casts = [ + 'isSelected' => 'boolean', + ]; - public function setIsChecked(string $value): void - { - $this->isChecked = $value === 'true'; - } + protected bool $isSelected = false; + protected string $description = ''; + + #[Override] public function render(): string { $data = [ - 'id' => $this->value, - 'name' => $this->name, + 'id' => $this->value, + 'name' => $this->name, 'class' => 'form-radio-btn bg-elevated', ]; - if ($this->required) { + if ($this->isRequired) { $data['required'] = 'required'; } + $this->mergeClass('relative w-full'); + + $descriptionText = ''; + if ($this->description !== '') { + $describerId = $this->name . 'Help'; + $descriptionText = <<{$this->description} + HTML; + $data['aria-describedby'] = $describerId; + } + $radioInput = form_radio( $data, - $this->value, - old($this->name) ? old($this->name) === $this->value : $this->isChecked, + $this->getValue(), + old($this->name) ? old($this->name) === $this->value : $this->isSelected, ); - $hint = $this->hint ? hint_tooltip($this->hint, 'ml-1 text-base') : ''; - return << +
getStringifiedAttributes()}"> {$radioInput} - +
HTML; } diff --git a/app/Views/Components/Forms/RadioGroup.php b/app/Views/Components/Forms/RadioGroup.php new file mode 100644 index 00000000..776ff8f2 --- /dev/null +++ b/app/Views/Components/Forms/RadioGroup.php @@ -0,0 +1,70 @@ + 'array', + ]; + + protected string $label; + + /** + * @var array{value:string,label:string,hint?:string} + */ + protected array $options = []; + + protected string $hint = ''; + + protected string $helper = ''; + + #[Override] + public function render(): string + { + $this->mergeClass('flex flex-col'); + $options = ''; + foreach ($this->options as $option) { + $radioButtonData = [ + 'value' => $option['value'], + 'name' => $this->name, + 'slot' => $option['label'], + 'description' => $option['description'] ?? '', + 'isSelected' => var_export($this->getValue() === '' ? $option['value'] === $this->options[0]['value'] : $option['value'] === $this->getValue(), true), + 'isRequired' => var_export($this->isRequired, true), + ]; + + $options .= new RadioButton($radioButtonData)->render(); + } + + $helperText = ''; + if ($this->helper !== '') { + $helperId = $this->name . 'Help'; + $helperText = new Helper([ + 'id' => $helperId, + 'slot' => $this->helper, + ])->render(); + $this->attributes['aria-describedby'] = $helperId; + } + + $hint = $this->hint === '' ? '' : new Hint([ + 'class' => 'ml-1', + 'slot' => $this->hint, + ])->render(); + + return <<getStringifiedAttributes()}> + {$this->label}{$hint} + {$helperText} +
{$options}
+ + HTML; + } +} diff --git a/app/Views/Components/Forms/Section.php b/app/Views/Components/Forms/Section.php index eedd05a5..d50baaed 100644 --- a/app/Views/Components/Forms/Section.php +++ b/app/Views/Components/Forms/Section.php @@ -4,23 +4,27 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; use ViewComponents\Component; class Section extends Component { - protected string $title = ''; + protected array $props = ['title', 'subtitle']; - protected ?string $subtitle = null; + protected string $title; - protected string $subtitleClass = ''; + protected string $subtitle = ''; + #[Override] public function render(): string { - $subtitle = $this->subtitle === null ? '' : '

' . $this->subtitle . '

'; + $subtitle = $this->subtitle === '' ? '' : '

' . $this->subtitle . '

'; + + $this->mergeClass('w-full p-4 sm:p-6 md:p-8 bg-elevated border-3 flex flex-col items-start border-subtle rounded-xl'); return << - {$this->title} +
getStringifiedAttributes()}> + {$this->title} {$subtitle}
{$this->slot}
diff --git a/app/Views/Components/Forms/Select.php b/app/Views/Components/Forms/Select.php index 1dce3393..b62c6942 100644 --- a/app/Views/Components/Forms/Select.php +++ b/app/Views/Components/Forms/Select.php @@ -4,33 +4,44 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; + class Select extends FormComponent { + protected array $props = ['options']; + + protected array $casts = [ + 'options' => 'array', + ]; + /** - * @var array + * @var array> */ protected array $options = []; - protected string $selected = ''; - - public function setOptions(string $value): void - { - $this->options = json_decode(htmlspecialchars_decode($value), true); - } - + #[Override] public function render(): string { + $this->mergeClass('w-full focus:border-contrast border-3 rounded-lg bg-elevated border-contrast'); $defaultAttributes = [ - 'class' => 'focus:border-contrast focus:ring-accent border-3 rounded-lg bg-elevated border-contrast ' . $this->class, - 'data-class' => $this->class, - 'data-select-text' => lang('Common.forms.multiSelect.selectText'), - 'data-loading-text' => lang('Common.forms.multiSelect.loadingText'), + 'data-select-text' => lang('Common.forms.multiSelect.selectText'), + 'data-loading-text' => lang('Common.forms.multiSelect.loadingText'), 'data-no-results-text' => lang('Common.forms.multiSelect.noResultsText'), 'data-no-choices-text' => lang('Common.forms.multiSelect.noChoicesText'), - 'data-max-item-text' => lang('Common.forms.multiSelect.maxItemText'), + 'data-max-item-text' => lang('Common.forms.multiSelect.maxItemText'), ]; - $extra = array_merge($this->attributes, $defaultAttributes); + $this->attributes = [...$defaultAttributes, ...$this->attributes]; - return form_dropdown($this->name, $this->options, old($this->name, $this->selected !== '' ? [$this->selected] : []), $extra); + $options = ''; + $selected = $this->getValue(); + foreach ($this->options as $option) { + $options .= ''; + } + + $this->attributes['name'] = $this->name; + + return <<getStringifiedAttributes()}>{$options} + HTML; } } diff --git a/app/Views/Components/Forms/SelectMulti.php b/app/Views/Components/Forms/SelectMulti.php new file mode 100644 index 00000000..6c09619b --- /dev/null +++ b/app/Views/Components/Forms/SelectMulti.php @@ -0,0 +1,52 @@ + 'array', + 'defaultValue' => 'array', + 'options' => 'array', + ]; + + /** + * @var array> + */ + protected array $options = []; + + #[Override] + public function render(): string + { + $this->mergeClass('w-full bg-elevated border-3 border-contrast rounded-lg relative'); + + $defaultAttributes = [ + 'multiple' => 'multiple', + 'data-select-text' => lang('Common.forms.multiSelect.selectText'), + 'data-loading-text' => lang('Common.forms.multiSelect.loadingText'), + 'data-no-results-text' => lang('Common.forms.multiSelect.noResultsText'), + 'data-no-choices-text' => lang('Common.forms.multiSelect.noChoicesText'), + 'data-max-item-text' => lang('Common.forms.multiSelect.maxItemText'), + ]; + + $this->attributes = [...$defaultAttributes, ...$this->attributes]; + + $options = ''; + $selected = $this->getValue(); + foreach ($this->options as $option) { + $options .= ''; + } + + $this->attributes['name'] = $this->name . '[]'; + + return <<getStringifiedAttributes()}>{$options} + HTML; + } +} diff --git a/app/Views/Components/Forms/Textarea.php b/app/Views/Components/Forms/Textarea.php index 705ec0f3..f58e3b98 100644 --- a/app/Views/Components/Forms/Textarea.php +++ b/app/Views/Components/Forms/Textarea.php @@ -4,25 +4,27 @@ declare(strict_types=1); namespace App\Views\Components\Forms; +use Override; + class Textarea extends FormComponent { - public function setValue(?string $value): void + protected array $attributes = [ + 'rows' => '6', + ]; + + public function setValue(string $value): void { - if ($value) { - $this->value = htmlspecialchars_decode($value); - } + $this->value = htmlspecialchars_decode($value); } + #[Override] public function render(): string { - unset($this->attributes['value']); + $this->mergeClass('bg-elevated w-full rounded-lg border-3 border-contrast focus:border-contrast focus-within:ring-accent transition'); - $this->attributes['class'] = 'bg-elevated w-full focus:border-contrast focus:ring-accent rounded-lg border-3 border-contrast ' . $this->class; + $this->attributes['id'] = $this->id; - $textarea = form_textarea( - $this->attributes, - old($this->name, $this->value ?? '', false) - ); + $textarea = form_textarea($this->attributes, $this->getValue()); return << 'boolean', + ]; protected string $hint = ''; - protected bool $checked = false; + protected string $helper = ''; - public function setChecked(string $value): void - { - $this->checked = $value === 'true'; - } + protected bool $isChecked = false; + #[Override] public function render(): string { - unset($this->attributes['checked']); + $this->mergeClass('relative justify-between inline-flex items-start gap-x-2'); - $wrapperClass = $this->class; - unset($this->attributes['class']); + $checkbox = form_checkbox( + [ + 'id' => $this->id, + 'name' => $this->name, + 'class' => 'form-switch', + ], + 'yes', + in_array($this->getValue(), ['yes', 'true', 'on', '1'], true), + ); - $sizeClass = [ - 'base' => 'form-switch-slider', - 'small' => 'form-switch-slider form-switch-slider--small', - ]; + $hint = $this->hint === '' ? '' : new Hint([ + 'class' => 'ml-1', + 'slot' => $this->hint, + ])->render(); - $this->attributes['class'] = 'form-switch'; + $helperText = ''; + if ($this->helper !== '') { + $helperId = $this->name . 'Help'; + $helperText = new Helper([ + 'id' => $helperId, + 'slot' => $this->helper, + 'class' => '-mt-1', + ])->render(); + $this->attributes['aria-describedby'] = $helperId; + } - $checkbox = form_checkbox($this->attributes, $this->value, old($this->name) === 'yes' ? true : $this->checked); - $hint = $this->hint === '' ? '' : hint_tooltip($this->hint, 'ml-1'); return << + HTML; } diff --git a/app/Views/Components/Forms/XMLEditor.php b/app/Views/Components/Forms/XMLEditor.php deleted file mode 100644 index fded2001..00000000 --- a/app/Views/Components/Forms/XMLEditor.php +++ /dev/null @@ -1,33 +0,0 @@ - - */ - protected array $attributes = [ - 'rows' => '5', - 'class' => 'textarea', - ]; - - protected string $content = ''; - - public function setContent(string $value): void - { - $this->content = htmlspecialchars_decode($value); - } - - public function render(): string - { - $this->attributes['slot'] = 'textarea'; - $textarea = form_textarea($this->attributes, $this->content); - - return <<{$textarea} - HTML; - } -} diff --git a/app/Views/Components/Heading.php b/app/Views/Components/Heading.php index 5530c50b..87d2de6e 100644 --- a/app/Views/Components/Heading.php +++ b/app/Views/Components/Heading.php @@ -4,10 +4,13 @@ declare(strict_types=1); namespace App\Views\Components; +use Override; use ViewComponents\Component; class Heading extends Component { + protected array $props = ['tagName', 'size']; + protected string $tagName = 'div'; /** @@ -15,18 +18,20 @@ class Heading extends Component */ protected string $size = 'base'; + #[Override] public function render(): string { - $sizeClasses = [ + $sizeClass = match ($this->size) { 'small' => 'tracking-wide text-base', - 'base' => 'text-xl', 'large' => 'text-3xl', - ]; + default => 'text-xl', + }; - $class = $this->class . ' relative z-10 font-bold text-heading-foreground font-display before:w-full before:absolute before:h-1/2 before:left-0 before:bottom-0 before:rounded-full before:bg-heading-background before:z-[-10] ' . $sizeClasses[$this->size]; + $this->mergeClass('relative z-10 font-bold text-heading-foreground font-display before:w-full before:absolute before:h-1/2 before:left-0 before:bottom-0 before:rounded-full before:bg-heading-background before:z-[-10]'); + $this->mergeClass($sizeClass); return <<tagName} class="{$class}">{$this->slot}tagName}> + <{$this->tagName} {$this->getStringifiedAttributes()}>{$this->slot}tagName}> HTML; } } diff --git a/app/Views/Components/Hint.php b/app/Views/Components/Hint.php new file mode 100644 index 00000000..c329296f --- /dev/null +++ b/app/Views/Components/Hint.php @@ -0,0 +1,30 @@ + 'bottom', + 'tabindex' => '0', + ]; + + #[Override] + public function render(): string + { + $this->attributes['title'] = $this->slot; + + $this->mergeClass('inline-block align-middle opacity-75'); + + $icon = icon('question-fill'); + + return <<getStringifiedAttributes()}>{$icon} + HTML; + } +} diff --git a/app/Views/Components/Icon.php b/app/Views/Components/Icon.php deleted file mode 100644 index f926eb45..00000000 --- a/app/Views/Components/Icon.php +++ /dev/null @@ -1,27 +0,0 @@ -glyph . '.svg'); - } catch (Exception) { - return '□'; - } - - unset($this->attributes['glyph']); - $attributes = stringify_attributes($this->attributes); - - return str_replace(' 'true', - 'title' => $attributes['slot'], + 'isSquared' => 'true', + 'title' => $attributes['slot'], 'data-tooltip' => 'bottom', ]; - $glyphSize = [ - 'small' => 'text-sm', - 'base' => 'text-lg', - 'large' => 'text-2xl', - ]; - - $allAttributes = array_merge($attributes, $iconButtonAttributes); + $allAttributes = [...$attributes, ...$iconButtonAttributes]; parent::__construct($allAttributes); - $this->slot = icon($this->glyph, $glyphSize[$this->size]); + $glyphSizeClass = match ($this->size) { + 'small' => 'text-sm', + 'large' => 'text-2xl', + default => 'text-lg', + }; + + $this->slot = (string) icon($this->glyph, [ + 'class' => $glyphSizeClass, + ]); } } diff --git a/app/Views/Components/Pill.php b/app/Views/Components/Pill.php index a6ff6ffa..9d76cc5e 100644 --- a/app/Views/Components/Pill.php +++ b/app/Views/Components/Pill.php @@ -4,38 +4,57 @@ declare(strict_types=1); namespace App\Views\Components; +use Override; use ViewComponents\Component; class Pill extends Component { + protected array $props = ['size', 'variant', 'icon', 'iconClass', 'hint']; + /** * @var 'small'|'base' */ - public string $size = 'base'; + protected string $size = 'base'; - public string $variant = 'default'; + protected string $variant = 'default'; - public ?string $icon = null; + protected string $icon = ''; - public ?string $iconClass = ''; + protected string $iconClass = ''; - protected ?string $hint = null; + protected string $hint = ''; + #[Override] public function render(): string { - $variantClasses = [ - 'default' => 'text-gray-800 bg-gray-100 border-gray-300', + $variantClass = match ($this->variant) { 'primary' => 'text-accent-contrast bg-accent-base border-accent-base', 'success' => 'text-pine-900 bg-pine-100 border-pine-300', - 'danger' => 'text-red-900 bg-red-100 border-red-300', + 'danger' => 'text-red-900 bg-red-100 border-red-300', 'warning' => 'text-yellow-900 bg-yellow-100 border-yellow-300', - ]; + default => 'text-gray-800 bg-gray-100 border-gray-300', + }; - $icon = $this->icon ? icon($this->icon, $this->iconClass) : ''; - $hint = $this->hint ? 'data-tooltip="bottom" title="' . $this->hint . '"' : ''; + $sizeClass = match ($this->size) { + 'small' => 'text-xs tracking-wide', + default => 'text-sm', + }; + + $icon = $this->icon !== '' ? icon($this->icon, [ + 'class' => $this->iconClass, + ]) : ''; + + if ($this->hint !== '') { + $this->attributes['data-tooltip'] = 'bottom'; + $this->attributes['title'] = $this->hint; + } + + $this->mergeClass('inline-flex lowercase items-center gap-x-1 px-1 font-semibold border rounded'); + $this->mergeClass($variantClass); + $this->mergeClass($sizeClass); return <<{$icon}{$this->slot} + getStringifiedAttributes()}>{$icon}{$this->slot} HTML; } } diff --git a/app/Views/Components/ReadMore.php b/app/Views/Components/ReadMore.php index 016a60b3..d55e48d9 100644 --- a/app/Views/Components/ReadMore.php +++ b/app/Views/Components/ReadMore.php @@ -4,21 +4,29 @@ declare(strict_types=1); namespace App\Views\Components; +use Override; use ViewComponents\Component; class ReadMore extends Component { - public string $id; + protected array $props = ['id']; + protected string $id; + + #[Override] public function render(): string { $readMoreLabel = lang('Common.read_more'); $readLessLabel = lang('Common.read_less'); + + $this->mergeClass('read-more'); + $this->attributes['style'] = '--line-clamp: 3'; + return << +
getStringifiedAttributes()}> -
{$this->slot}
- +
{$this->slot}
+
HTML; } diff --git a/app/Views/Components/SeeMore.php b/app/Views/Components/SeeMore.php index 19f247f2..e8e42deb 100644 --- a/app/Views/Components/SeeMore.php +++ b/app/Views/Components/SeeMore.php @@ -4,19 +4,25 @@ declare(strict_types=1); namespace App\Views\Components; +use Override; use ViewComponents\Component; class SeeMore extends Component { + #[Override] public function render(): string { $seeMoreLabel = lang('Common.see_more'); $seeLessLabel = lang('Common.see_less'); + + $this->mergeClass('see-more'); + $this->attributes['styles'] = '--content-height: 10rem'; + return << +
getStringifiedAttributes()}> -
{$this->slot}
- +
{$this->slot}
+
HTML; } diff --git a/app/Views/_message_block.php b/app/Views/_message_block.php index 1504aa37..4f40c4e5 100644 --- a/app/Views/_message_block.php +++ b/app/Views/_message_block.php @@ -1,18 +1,18 @@ has('message')): ?> - + has('error')): ?> - + has('errors')): ?>
    -
  • +
diff --git a/app/Views/errors/cli/error_exception.php b/app/Views/errors/cli/error_exception.php index b9c4941c..7a7247fc 100644 --- a/app/Views/errors/cli/error_exception.php +++ b/app/Views/errors/cli/error_exception.php @@ -5,17 +5,26 @@ declare(strict_types=1); use CodeIgniter\CLI\CLI; // The main Exception -CLI::newLine(); -CLI::write('[' . get_class($exception) . ']', 'light_gray', 'red'); -CLI::newLine(); +CLI::write('[' . $exception::class . ']', 'light_gray', 'red'); CLI::write($message); -CLI::newLine(); CLI::write('at ' . CLI::color(clean_path($exception->getFile()) . ':' . $exception->getLine(), 'green')); CLI::newLine(); +$last = $exception; + +while ($prevException = $last->getPrevious()) { + $last = $prevException; + + CLI::write(' Caused by:'); + CLI::write(' [' . $prevException::class . ']', 'red'); + CLI::write(' ' . $prevException->getMessage()); + CLI::write(' at ' . CLI::color(clean_path($prevException->getFile()) . ':' . $prevException->getLine(), 'green')); + CLI::newLine(); +} + // The backtrace if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) { - $backtraces = $exception->getTrace(); + $backtraces = $last->getTrace(); if ($backtraces) { CLI::write('Backtrace:', 'green'); @@ -27,9 +36,9 @@ if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) { $c = str_pad((string) ($i + 1), 3, ' ', STR_PAD_LEFT); if (isset($error['file'])) { - $filepath = clean_path($error['file']) . ':' . $error['line']; + $fileKey = clean_path($error['file']) . ':' . $error['line']; - CLI::write($c . $padFile . CLI::color($filepath, 'yellow')); + CLI::write($c . $padFile . CLI::color($fileKey, 'yellow')); } else { CLI::write($c . $padFile . CLI::color('[internal function]', 'yellow')); } @@ -43,20 +52,11 @@ if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) { $function .= $padClass . $error['function']; } - $args = implode(', ', array_map(static function ($value) { - switch (true) { - case is_object($value): - return 'Object(' . get_class($value) . ')'; - - case is_array($value): - return count($value) ? '[...]' : '[]'; - - case $value === null: - return 'null'; // return the lowercased version - - default: - return var_export($value, true); - } + $args = implode(', ', array_map(static fn ($value) => match (true) { + is_object($value) => 'Object(' . $value::class . ')', + is_array($value) => $value !== [] ? '[...]' : '[]', + $value === null => 'null', // return the lowercased version + default => var_export($value, true), }, array_values($error['args'] ?? []))); $function .= '(' . $args . ')'; diff --git a/app/Views/errors/html/debug.css b/app/Views/errors/html/debug.css index 2ef43583..3b6de5b6 100644 --- a/app/Views/errors/html/debug.css +++ b/app/Views/errors/html/debug.css @@ -3,7 +3,7 @@ --main-text-color: #555; --dark-text-color: #222; --light-text-color: #c7c7c7; - --brand-primary-color: #e06e3f; + --brand-primary-color: #dc4814; --light-bg-color: #ededee; --dark-bg-color: #404040; } @@ -11,69 +11,81 @@ body { height: 100%; background: var(--main-bg-color); - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, - sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji"; color: var(--main-text-color); font-weight: 300; margin: 0; padding: 0; } + h1 { font-weight: lighter; - letter-spacing: 0.8; font-size: 3rem; color: var(--dark-text-color); margin: 0; } + h1.headline { margin-top: 20%; font-size: 5rem; } + .text-center { text-align: center; } + p.lead { font-size: 1.6rem; } + .container { max-width: 75rem; margin: 0 auto; padding: 1rem; } + .header { background: var(--light-bg-color); color: var(--dark-text-color); + margin-top: 2.17rem; } + .header .container { - padding: 1rem 1.75rem 1.75rem 1.75rem; + padding: 1rem; } + .header h1 { font-size: 2.5rem; font-weight: 500; } + .header p { font-size: 1.2rem; margin: 0; line-height: 2.5; } + .header a { color: var(--brand-primary-color); margin-left: 2rem; display: none; text-decoration: none; } + .header:hover a { display: inline; } -.footer { - background: var(--dark-bg-color); - color: var(--light-text-color); -} -.footer .container { - border-top: 1px solid #e7e7e7; - margin-top: 1rem; +.environment { + background: var(--brand-primary-color); + color: var(--main-bg-color); text-align: center; + padding: calc(4px + 0.2083vw); + width: 100%; + top: 0; + position: fixed; } .source { @@ -86,17 +98,21 @@ p.lead { margin: 0; overflow-x: scroll; } + .source span.line { line-height: 1.4; } + .source span.line .number { color: #666; } + .source .line .highlight { display: block; background: var(--dark-text-color); color: var(--light-text-color); } + .source span.highlight .number { color: #fff; } @@ -108,37 +124,44 @@ p.lead { padding: 0; margin-bottom: -1px; } + .tabs li { display: inline; } + .tabs a:link, .tabs a:visited { - padding: 0rem 1rem; + padding: 0 1rem; line-height: 2.7; text-decoration: none; color: var(--dark-text-color); background: var(--light-bg-color); - border: 1px solid rgba(0, 0, 0, 0.15); + border: 1px solid rgb(0 0 0 / 15%); border-bottom: 0; border-top-left-radius: 5px; border-top-right-radius: 5px; display: inline-block; } + .tabs a:hover { background: var(--light-bg-color); - border-color: rgba(0, 0, 0, 0.15); + border-color: rgb(0 0 0 / 15%); } + .tabs a.active { background: var(--main-bg-color); color: var(--main-text-color); } + .tab-content { background: var(--main-bg-color); - border: 1px solid rgba(0, 0, 0, 0.15); + border: 1px solid rgb(0 0 0 / 15%); } + .content { padding: 1rem; } + .hide { display: none; } @@ -153,26 +176,26 @@ p.lead { border-radius: 5px; color: #31708f; } -ul, -ol { - line-height: 1.8; -} table { width: 100%; overflow: hidden; } + th { text-align: left; border-bottom: 1px solid #e7e7e7; padding-bottom: 0.5rem; } + td { padding: 0.2rem 0.5rem 0.2rem 0; } + tr:hover td { background: #f1f1f1; } + td pre { white-space: pre-wrap; } @@ -180,20 +203,25 @@ td pre { .trace a { color: inherit; } + .trace table { width: auto; } + .trace tr td:first-child { min-width: 5em; font-weight: bold; } + .trace td { background: var(--light-bg-color); padding: 0 1rem; } + .trace td pre { margin: 0; } + .args { display: none; } diff --git a/app/Views/errors/html/debug.js b/app/Views/errors/html/debug.js index 3428750f..65254785 100644 --- a/app/Views/errors/html/debug.js +++ b/app/Views/errors/html/debug.js @@ -5,6 +5,7 @@ var tabLinks = []; var contentDivs = []; +// eslint-disable-next-line @typescript-eslint/no-unused-vars function init() { // Grab the tab links and content divs from the page var tabListItems = document.getElementById("tabs").childNodes; @@ -85,6 +86,7 @@ function getHash(url) { //-------------------------------------------------------------------- +// eslint-disable-next-line @typescript-eslint/no-unused-vars function toggle(elem) { elem = document.getElementById(elem); diff --git a/app/Views/errors/html/error_400.php b/app/Views/errors/html/error_400.php new file mode 100644 index 00000000..555da042 --- /dev/null +++ b/app/Views/errors/html/error_400.php @@ -0,0 +1,84 @@ + + + + + <?= lang('Errors.badRequest') ?> + + + + +
+

400

+ +

+ + + + + +

+
+ + diff --git a/app/Views/errors/html/error_403.php b/app/Views/errors/html/error_403.php new file mode 100644 index 00000000..a5d4d040 --- /dev/null +++ b/app/Views/errors/html/error_403.php @@ -0,0 +1,30 @@ + + + + + + + + + 403 Forbidden + ' /> + asset('styles/index.css', 'css') ?> + + + + +

403 - Forbidden

+ +

+ + + + You do not have sufficient permissions to access that page. + +

+ + + + diff --git a/app/Views/errors/html/error_404.php b/app/Views/errors/html/error_404.php index ed288ac1..4d74a150 100644 --- a/app/Views/errors/html/error_404.php +++ b/app/Views/errors/html/error_404.php @@ -1,12 +1,13 @@ - + - 404 Page Not Found + <?= lang('Errors.pageNotFound') ?> ' /> asset('styles/index.css', 'css') ?> @@ -14,16 +15,16 @@ -

404 - File Not Found

+

404

- Sorry! Cannot seem to find the page you were looking for. +

- + diff --git a/app/Views/errors/html/error_exception.php b/app/Views/errors/html/error_exception.php index e0723098..1931636f 100644 --- a/app/Views/errors/html/error_exception.php +++ b/app/Views/errors/html/error_exception.php @@ -1,6 +1,12 @@ - +declare(strict_types=1); + +use CodeIgniter\CodeIgniter; +use CodeIgniter\HTTP\Header; + +$errorId = uniqid('error', true); +?> @@ -8,11 +14,11 @@ $error_id = uniqid('error', true); ?> <?= esc($title) ?> - - @@ -20,6 +26,12 @@ $error_id = uniqid('error', true); ?>
+
+ Displayed at — + PHP: — + CodeIgniter: -- + Environment: +

getCode() ? ' #' . $exception->getCode() : '') ?>

@@ -41,6 +53,30 @@ $error_id = uniqid('error', true); ?>

+
+ getPrevious()) { + $last = $prevException; + ?> + +
+    Caused by:
+    getCode() ? ' #' . $prevException->getCode() : '') ?>
+
+    getMessage())) ?>
+    getMessage())) ?>"
+       rel="noreferrer" target="_blank">search →
+    getFile()) . ':' . $prevException->getLine()) ?>
+    
+ + +
+ +
    @@ -63,14 +99,14 @@ $error_id = uniqid('error', true); ?>
  • - + + if (isset($row['function']) && in_array($row['function'], ['include', 'include_once', 'require', 'require_once'], true)) { + echo esc($row['function'] . ' ' . clean_path($row['file'])); + } else { + echo esc(clean_path($row['file']) . ' : ' . $row['line']); + } + ?> {PHP internal code} @@ -79,20 +115,20 @@ $error_id = uniqid('error', true); ?>   —   - - ( arguments ) -

    + + ( arguments ) +
    getParameters(); - } + $params = null; + // Reflection by name is not available for closure function + if (! str_ends_with($row['function'], '}')) { + $mirror = isset($row['class']) ? new ReflectionMethod($row['class'], $row['function']) : new ReflectionFunction($row['function']); + $params = $mirror->getParameters(); + } - foreach ($row['args'] as $key => $value) : ?> + foreach ($row['args'] as $key => $value) : ?> @@ -191,7 +227,7 @@ $error_id = uniqid('error', true); ?>
    - +
    name : "#{$key}") ?>
    @@ -201,7 +237,7 @@ $error_id = uniqid('error', true); ?> - + @@ -285,22 +321,22 @@ $error_id = uniqid('error', true); ?> - - - - - - - - - + $value) : ?> + + + + +
    HTTP MethodgetMethod())) ?>getMethod()) ?>
    IP Address
    getName(), 'html') ?>getValueLine(), 'html') ?>
    + getValueLine(), 'html'); + } else { + foreach ($value as $i => $header) { + echo ' (' . $i + 1 . ') ' . esc($header->getValueLine(), 'html'); + } + } + ?> +
    @@ -309,9 +345,9 @@ $error_id = uniqid('error', true); ?> setStatusCode(http_response_code()); - ?> + $response = service('response'); +$response->setStatusCode(http_response_code()); +?>
    @@ -322,8 +358,6 @@ $error_id = uniqid('error', true); ?> headers(); ?> - -

    Headers

    @@ -334,12 +368,22 @@ $error_id = uniqid('error', true); ?> - $value) : ?> - - - - - + $value) : ?> + + + + +
    getHeaderLine($name), 'html') ?>
    + getHeaderLine($name), 'html'); + } else { + foreach ($value as $i => $header) { + echo ' (' . $i + 1 . ') ' . esc($header->getValueLine(), 'html'); + } + } + ?> +
    @@ -382,18 +426,7 @@ $error_id = uniqid('error', true); ?>
    - - + diff --git a/app/Views/errors/html/production.php b/app/Views/errors/html/production.php index 1ddf8306..77445e16 100644 --- a/app/Views/errors/html/production.php +++ b/app/Views/errors/html/production.php @@ -7,41 +7,45 @@ - Whoops! + <?= lang('Errors.whoops') ?> ' /> asset('styles/index.css', 'css') ?> - isLoggedIn()): ?> + loggedIn()): ?> asset('js/error.ts', 'js') ?> - isLoggedIn()): ?> + loggedIn()): ?>
    -

    Whoops!

    -

    We seem to have hit a snag. Please try again later...

    +

    +

    getCode() ? ' #' . $exception->getCode() : '') ?>

    getMessage())) ?>
    at getFile())) ?>:getLine()) ?>

    - - Copy stack trace - + + 'mr-2', + ]) ?>Copy stack trace +

    Found a bug?

    -

    You can help get it fixed by creating an issue on the Castopod issue tracker. Please check that the issue does not already exist beforehand.

    +

    You can help get it fixed by creating an issue on the Castopod issue tracker. Please check that the issue does not already exist beforehand.

    Not sure what's happening?

    -

    You can ask for help in the Castopod community chat!

    +

    You can ask for help in the Castopod community chat!

    diff --git a/app/Views/pager/default_full.php b/app/Views/pager/default_full.php index 7d942e38..e82fefa8 100644 --- a/app/Views/pager/default_full.php +++ b/app/Views/pager/default_full.php @@ -11,18 +11,18 @@ $pager->setSurroundCount(2); hasPreviousPage()): ?>
  • + 'Pager.first', + ) ?>" class="block px-3 py-2 text-skin-muted hover:bg-highlight">
  • + 'Pager.previous', + ) ?>" class="block px-3 py-2 text-skin-muted hover:bg-highlight"> + 'Pager.previous', + ) ?>
  • @@ -34,9 +34,7 @@ $pager->setSurroundCount(2); - + @@ -46,15 +44,15 @@ $pager->setSurroundCount(2); hasNextPage()): ?>
  • + 'Pager.next', + ) ?>" class="block px-3 py-2 text-skin-muted hover:bg-highlight">
  • + 'Pager.last', + ) ?>" class="block px-3 py-2 text-skin-muted hover:bg-highlight">
  • diff --git a/app/index.html b/app/index.html index 5caba05a..242d70c1 100644 --- a/app/index.html +++ b/app/index.html @@ -1,4 +1,4 @@ - + 403 Forbidden diff --git a/builds b/builds index cc2ca085..e7884e0b 100644 --- a/builds +++ b/builds @@ -1,6 +1,8 @@ #!/usr/bin/env php vendor/opawg/user-agents-php/src/UserAgents.php", - "@php vendor/opawg/user-agents-php/src/UserAgentsRSSGenerate.php > vendor/opawg/user-agents-php/src/UserAgentsRSS.php", + "@php vendor/opawg/user-agents-v2-php/src/UserAgentsGenerate.php > vendor/opawg/user-agents-v2-php/src/UserAgents.php", + "@php vendor/opawg/user-agents-v2-php/src/UserAgentsRSSGenerate.php > vendor/opawg/user-agents-v2-php/src/UserAgentsRSS.php", "@php vendor/adaures/ipcat-php/src/IpDbGenerate.php > vendor/adaures/ipcat-php/src/IpDb.php", "@php vendor/adaures/podcast-persons-taxonomy/src/TaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-en.json > modules/Admin/Language/en/PersonsTaxonomy.php", "@php vendor/adaures/podcast-persons-taxonomy/src/TaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-fr.json > modules/Admin/Language/fr/PersonsTaxonomy.php", - "@php vendor/adaures/podcast-persons-taxonomy/src/ReversedTaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-en.json > vendor/adaures/podcast-persons-taxonomy/src/ReversedTaxonomy.php" + "@php vendor/adaures/podcast-persons-taxonomy/src/ReversedTaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-en.json > vendor/adaures/podcast-persons-taxonomy/src/ReversedTaxonomy.php", + "vendor/bin/php-icons init && vendor/bin/php-icons scan" ], "post-update-cmd": [ "@composer dump-autoload", - "@php vendor/opawg/user-agents-php/src/UserAgentsGenerate.php > vendor/opawg/user-agents-php/src/UserAgents.php", - "@php vendor/opawg/user-agents-php/src/UserAgentsRSSGenerate.php > vendor/opawg/user-agents-php/src/UserAgentsRSS.php", + "@php vendor/opawg/user-agents-v2-php/src/UserAgentsGenerate.php > vendor/opawg/user-agents-v2-php/src/UserAgents.php", + "@php vendor/opawg/user-agents-v2-php/src/UserAgentsRSSGenerate.php > vendor/opawg/user-agents-v2-php/src/UserAgentsRSS.php", "@php vendor/adaures/ipcat-php/src/IpDbGenerate.php > vendor/adaures/ipcat-php/src/IpDb.php", "@php vendor/adaures/podcast-persons-taxonomy/src/TaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-en.json > modules/Admin/Language/en/PersonsTaxonomy.php", "@php vendor/adaures/podcast-persons-taxonomy/src/TaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-fr.json > modules/Admin/Language/fr/PersonsTaxonomy.php", - "@php vendor/adaures/podcast-persons-taxonomy/src/ReversedTaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-en.json > vendor/adaures/podcast-persons-taxonomy/src/ReversedTaxonomy.php" + "@php vendor/adaures/podcast-persons-taxonomy/src/ReversedTaxonomyGenerate.php https://raw.githubusercontent.com/Podcastindex-org/podcast-namespace/main/taxonomy-en.json > vendor/adaures/podcast-persons-taxonomy/src/ReversedTaxonomy.php", + "vendor/bin/php-icons init && vendor/bin/php-icons scan" ] }, "support": { "source": "https://code.castopod.org/adaures/castopod.git", "discord": "https://castopod.org/discord" }, + "minimum-stability": "dev", "prefer-stable": true, "config": { + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true, "allow-plugins": { "phpstan/extension-installer": true } - } + }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/codeigniter4/tasks.git" + } + ] } diff --git a/composer.lock b/composer.lock index 0eaf6f04..3e35a5dd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,63 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "caa3b9ff10584fe03c7be1176713b427", + "content-hash": "de1c665976cbb37fec3de326336d83f5", "packages": [ + { + "name": "adaures/castopod-plugins-manager", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/ad-aures/castopod-plugins-manager.git", + "reference": "53430f9a57cd38eee3e3dfe5953764cc42c2a0c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ad-aures/castopod-plugins-manager/zipball/53430f9a57cd38eee3e3dfe5953764cc42c2a0c9", + "reference": "53430f9a57cd38eee3e3dfe5953764cc42c2a0c9", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "z4kn4fein/php-semver": "^3.0" + }, + "require-dev": { + "pestphp/pest": "^4.0.4", + "pestphp/pest-plugin-type-coverage": "^4.0.2", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "rector/rector": "^2.1.4", + "symplify/coding-standard": "^12.4.3", + "symplify/easy-coding-standard": "^12.5.24" + }, + "default-branch": true, + "type": "library", + "autoload": { + "files": [ + "src/Constants.php", + "src/helpers.php" + ], + "psr-4": { + "Castopod\\PluginsManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "AGPL-3.0-only" + ], + "authors": [ + { + "name": "Yassine Doghri", + "homepage": "https://yassinedoghri.com/" + } + ], + "description": "A PHP library to install, update, and remove plugins on a Castopod instance.", + "support": { + "issues": "https://github.com/ad-aures/castopod-plugins-manager/issues", + "source": "https://github.com/ad-aures/castopod-plugins-manager/tree/main" + }, + "time": "2025-10-06T15:58:43+00:00" + }, { "name": "adaures/ipcat-php", "version": "v1.0.0", @@ -24,7 +79,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["GPL-3.0-only"], + "license": [ + "GPL-3.0-only" + ], "authors": [ { "name": "Benjamin Bellamy", @@ -43,11 +100,11 @@ }, { "name": "adaures/podcast-persons-taxonomy", - "version": "v1.0.0", + "version": "v1.0.1", "source": { "type": "git", "url": "https://code.castopod.org/adaures/podcast-persons-taxonomy", - "reference": "117b207e334f54cd1b00ee52b0c53d58cb1a5524" + "reference": "d2a6836e32ed013676fd969425e133e0ebb104fc" }, "type": "library", "autoload": { @@ -56,7 +113,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Benjamin Bellamy", @@ -71,30 +130,253 @@ ], "description": "Generate PHP translation files for CodeIgniter4 from the podcast-namespace's Persons Taxonomy json files.", "homepage": "https://code.castopod.org/adaures/podcast-persons-taxonomy", - "time": "2022-02-20T14:09:25+00:00" + "time": "2023-06-22T14:24:55+00:00" }, { - "name": "brick/math", - "version": "0.10.2", + "name": "adhocore/cli", + "version": "v1.9.4", "source": { "type": "git", - "url": "https://github.com/brick/math.git", - "reference": "459f2781e1a08d52ee56b0b1444086e038561e3f" + "url": "https://github.com/adhocore/php-cli.git", + "reference": "474dc3d7ab139796be98b104d891476e3916b6f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/459f2781e1a08d52ee56b0b1444086e038561e3f", - "reference": "459f2781e1a08d52ee56b0b1444086e038561e3f", + "url": "https://api.github.com/repos/adhocore/php-cli/zipball/474dc3d7ab139796be98b104d891476e3916b6f4", + "reference": "474dc3d7ab139796be98b104d891476e3916b6f4", "shasum": "" }, "require": { + "php": ">=8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ahc\\Cli\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jitendra Adhikari", + "email": "jiten.adhikary@gmail.com" + } + ], + "description": "Command line interface library for PHP", + "keywords": [ + "argument-parser", + "argv-parser", + "cli", + "cli-action", + "cli-app", + "cli-color", + "cli-option", + "cli-writer", + "command", + "console", + "console-app", + "php-cli", + "php8", + "stream-input", + "stream-output" + ], + "support": { + "issues": "https://github.com/adhocore/php-cli/issues", + "source": "https://github.com/adhocore/php-cli/tree/v1.9.4" + }, + "funding": [ + { + "url": "https://paypal.me/ji10", + "type": "custom" + }, + { + "url": "https://github.com/adhocore", + "type": "github" + } + ], + "time": "2025-05-11T13:23:54+00:00" + }, + { + "name": "aws/aws-crt-php", + "version": "v1.2.7", + "source": { + "type": "git", + "url": "https://github.com/awslabs/aws-crt-php.git", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" + } + ], + "description": "AWS Common Runtime for PHP", + "homepage": "https://github.com/awslabs/aws-crt-php", + "keywords": [ + "amazon", + "aws", + "crt", + "sdk" + ], + "support": { + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7" + }, + "time": "2024-10-18T22:15:13+00:00" + }, + { + "name": "aws/aws-sdk-php", + "version": "3.369.37", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "bc599ce989b101ee630d5e1a1aeda387632df47b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/bc599ce989b101ee630d5e1a1aeda387632df47b", + "reference": "bc599ce989b101ee630d5e1a1aeda387632df47b", + "shasum": "" + }, + "require": { + "aws/aws-crt-php": "^1.2.3", "ext-json": "*", - "php": "^7.4 || ^8.0" + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/promises": "^2.0", + "guzzlehttp/psr7": "^2.4.5", + "mtdowling/jmespath.php": "^2.8.0", + "php": ">=8.1", + "psr/http-message": "^1.0 || ^2.0", + "symfony/filesystem": "^v5.4.45 || ^v6.4.3 || ^v7.1.0 || ^v8.0.0" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "composer/composer": "^2.7.8", + "dms/phpunit-arraysubset-asserts": "^0.4.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-sockets": "*", + "phpunit/phpunit": "^9.6", + "psr/cache": "^2.0 || ^3.0", + "psr/simple-cache": "^2.0 || ^3.0", + "sebastian/comparator": "^1.2.3 || ^4.0 || ^5.0", + "yoast/phpunit-polyfills": "^2.0" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-pcntl": "To use client-side monitoring", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Aws\\": "src/" + }, + "exclude-from-classmap": [ + "src/data/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "support": { + "forum": "https://github.com/aws/aws-sdk-php/discussions", + "issues": "https://github.com/aws/aws-sdk-php/issues", + "source": "https://github.com/aws/aws-sdk-php/tree/3.369.37" + }, + "time": "2026-02-18T19:16:34+00:00" + }, + { + "name": "brick/math", + "version": "0.14.8", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629", + "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629", + "shasum": "" + }, + "require": { + "php": "^8.2" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^9.0", - "vimeo/psalm": "4.25.0" + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" }, "type": "library", "autoload": { @@ -103,7 +385,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "description": "Arbitrary-precision arithmetic library", "keywords": [ "Arbitrary-precision", @@ -112,12 +396,17 @@ "arithmetic", "bigdecimal", "bignum", + "bignumber", "brick", - "math" + "decimal", + "integer", + "math", + "mathematics", + "rational" ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.10.2" + "source": "https://github.com/brick/math/tree/0.14.8" }, "funding": [ { @@ -125,7 +414,7 @@ "type": "github" } ], - "time": "2022-08-10T22:54:19+00:00" + "time": "2026-02-10T14:33:43+00:00" }, { "name": "chrisjean/php-ico", @@ -147,10 +436,14 @@ }, "type": "library", "autoload": { - "classmap": ["class-php-ico.php"] + "classmap": [ + "class-php-ico.php" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["GPL-2.0+"], + "license": [ + "GPL-2.0+" + ], "authors": [ { "name": "Chris Jean", @@ -160,7 +453,10 @@ ], "description": "An easy-to-use library to generate valid ICO files.", "homepage": "https://github.com/chrisbliss18/php-ico", - "keywords": ["favicon", "ico"], + "keywords": [ + "favicon", + "ico" + ], "support": { "issues": "https://github.com/chrisbliss18/php-ico/issues", "source": "https://github.com/chrisbliss18/php-ico" @@ -168,108 +464,257 @@ "time": "2016-09-27T22:00:56+00:00" }, { - "name": "codeigniter4/framework", - "version": "v4.2.7", + "name": "cocur/slugify", + "version": "v4.7.1", "source": { "type": "git", - "url": "https://github.com/codeigniter4/framework.git", - "reference": "011ce3bbda6f85930075a9b8fecbee01c4b23ab9" + "url": "https://github.com/cocur/slugify.git", + "reference": "a860dab2b9f5f37775fc6414d4f049434848165f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/codeigniter4/framework/zipball/011ce3bbda6f85930075a9b8fecbee01c4b23ab9", - "reference": "011ce3bbda6f85930075a9b8fecbee01c4b23ab9", + "url": "https://api.github.com/repos/cocur/slugify/zipball/a860dab2b9f5f37775fc6414d4f049434848165f", + "reference": "a860dab2b9f5f37775fc6414d4f049434848165f", "shasum": "" }, "require": { - "ext-curl": "*", - "ext-intl": "*", - "ext-json": "*", "ext-mbstring": "*", - "kint-php/kint": "^4.2", - "laminas/laminas-escaper": "^2.9", - "php": "^7.4 || ^8.0", - "psr/log": "^1.1" + "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "conflict": { + "symfony/config": "<3.4 || >=4,<4.3", + "symfony/dependency-injection": "<3.4 || >=4,<4.3", + "symfony/http-kernel": "<3.4 || >=4,<4.3", + "twig/twig": "<2.12.1" }, "require-dev": { - "codeigniter/coding-standard": "^1.5", - "fakerphp/faker": "^1.9", - "friendsofphp/php-cs-fixer": "~3.11.0", - "mikey179/vfsstream": "^1.6", + "laravel/framework": "^5.0|^6.0|^7.0|^8.0", + "latte/latte": "~2.2", + "league/container": "^2.2.0", + "mikey179/vfsstream": "~1.6.8", + "mockery/mockery": "^1.3", + "nette/di": "~2.4", + "pimple/pimple": "~1.1", + "plumphp/plum": "~0.1", + "symfony/config": "^3.4 || ^4.3 || ^5.0 || ^6.0", + "symfony/dependency-injection": "^3.4 || ^4.3 || ^5.0 || ^6.0", + "symfony/http-kernel": "^3.4 || ^4.3 || ^5.0 || ^6.0", + "symfony/phpunit-bridge": "^5.4 || ^6.0", + "twig/twig": "^2.12.1 || ~3.0", + "zendframework/zend-modulemanager": "~2.2", + "zendframework/zend-servicemanager": "~2.2", + "zendframework/zend-view": "~2.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cocur\\Slugify\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florian Eckerstorfer", + "email": "florian@eckerstorfer.co", + "homepage": "https://florian.ec" + }, + { + "name": "Ivo Bathke", + "email": "ivo.bathke@gmail.com" + } + ], + "description": "Converts a string into a slug.", + "keywords": [ + "slug", + "slugify" + ], + "support": { + "issues": "https://github.com/cocur/slugify/issues", + "source": "https://github.com/cocur/slugify/tree/v4.7.1" + }, + "time": "2025-11-27T18:57:36+00:00" + }, + { + "name": "codeigniter4/framework", + "version": "v4.7.0", + "source": { + "type": "git", + "url": "https://github.com/codeigniter4/framework.git", + "reference": "e7753bc03f8b74af428f46b5e2bb74925487c930" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/codeigniter4/framework/zipball/e7753bc03f8b74af428f46b5e2bb74925487c930", + "reference": "e7753bc03f8b74af428f46b5e2bb74925487c930", + "shasum": "" + }, + "require": { + "ext-intl": "*", + "ext-mbstring": "*", + "laminas/laminas-escaper": "^2.18", + "php": "^8.2", + "psr/log": "^3.0" + }, + "require-dev": { + "codeigniter/coding-standard": "^1.7", + "fakerphp/faker": "^1.24", + "friendsofphp/php-cs-fixer": "^3.47.1", + "kint-php/kint": "^6.1", + "mikey179/vfsstream": "^1.6.12", "nexusphp/cs-config": "^3.6", - "phpunit/phpunit": "^9.1", - "predis/predis": "^1.1 || ^2.0" + "phpunit/phpunit": "^10.5.16 || ^11.2", + "predis/predis": "^3.0" }, "suggest": { + "ext-apcu": "If you use Cache class ApcuHandler", + "ext-curl": "If you use CURLRequest class", + "ext-dom": "If you use TestResponse", + "ext-exif": "If you run Image class tests", "ext-fileinfo": "Improves mime type detection for files", + "ext-gd": "If you use Image class GDHandler", "ext-imagick": "If you use Image class ImageMagickHandler", + "ext-libxml": "If you use TestResponse", "ext-memcache": "If you use Cache class MemcachedHandler with Memcache", "ext-memcached": "If you use Cache class MemcachedHandler with Memcached", "ext-mysqli": "If you use MySQL", "ext-oci8": "If you use Oracle Database", + "ext-pcntl": "If you use Signals", "ext-pgsql": "If you use PostgreSQL", + "ext-posix": "If you use Signals", "ext-readline": "Improves CLI::input() usability", "ext-redis": "If you use Cache class RedisHandler", "ext-simplexml": "If you format XML", + "ext-sodium": "If you use Encryption SodiumHandler", "ext-sqlite3": "If you use SQLite3", - "ext-sqlsrv": "If you use SQL Server" + "ext-sqlsrv": "If you use SQL Server", + "ext-xdebug": "If you use CIUnitTestCase::assertHeaderEmitted()" }, "type": "project", "autoload": { "psr-4": { "CodeIgniter\\": "system/" }, - "exclude-from-classmap": ["**/Database/Migrations/**"] + "exclude-from-classmap": [ + "**/Database/Migrations/**" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "description": "The CodeIgniter framework v4", "homepage": "https://codeigniter.com", "support": { - "forum": "http://forum.codeigniter.com/", + "forum": "https://forum.codeigniter.com/", "slack": "https://codeigniterchat.slack.com", "source": "https://github.com/codeigniter4/CodeIgniter4" }, - "time": "2022-10-06T13:46:23+00:00" + "time": "2026-02-01T20:39:35+00:00" }, { - "name": "codeigniter4/settings", - "version": "v2.1.0", + "name": "codeigniter4/queue", + "version": "dev-develop", "source": { "type": "git", - "url": "https://github.com/codeigniter4/settings.git", - "reference": "0c3fbd5a474eeb592bef5550be2a6d2f73a8a09d" + "url": "https://github.com/codeigniter4/queue.git", + "reference": "b44386ad29f0a2124e59582ef5ba170788b926ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/codeigniter4/settings/zipball/0c3fbd5a474eeb592bef5550be2a6d2f73a8a09d", - "reference": "0c3fbd5a474eeb592bef5550be2a6d2f73a8a09d", + "url": "https://api.github.com/repos/codeigniter4/queue/zipball/b44386ad29f0a2124e59582ef5ba170788b926ed", + "reference": "b44386ad29f0a2124e59582ef5ba170788b926ed", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0" + "php": "^8.2" }, "require-dev": { - "codeigniter/coding-standard": "^1.1", - "codeigniter4/codeigniter4": "dev-develop", - "fakerphp/faker": "^1.9", - "mockery/mockery": "^1.0", - "nexusphp/cs-config": "^3.1", - "nexusphp/tachycardia": "^1.0", - "php-coveralls/php-coveralls": "^2.4", - "phpstan/phpstan": "^1.0", - "phpunit/phpunit": "^9.0", - "squizlabs/php_codesniffer": "^3.3" + "codeigniter4/devkit": "^1.3", + "codeigniter4/framework": "^4.3", + "php-amqplib/php-amqplib": "^3.7", + "phpstan/phpstan-strict-rules": "^2.0", + "predis/predis": "^2.0" + }, + "suggest": { + "ext-redis": "If you want to use RedisHandler", + "php-amqplib/php-amqplib": "If you want to use RabbitMQHandler", + "predis/predis": "If you want to use PredisHandler" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "CodeIgniter\\Queue\\": "src" + }, + "exclude-from-classmap": [ + "**/Database/Migrations/**" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "michalsn", + "homepage": "https://github.com/michalsn", + "role": "Developer" + } + ], + "description": "Queues for CodeIgniter 4 framework", + "homepage": "https://github.com/codeigniter4/queue", + "keywords": [ + "codeigniter", + "codeigniter4", + "database", + "predis", + "queue", + "redis" + ], + "support": { + "issues": "https://github.com/codeigniter4/queue/issues", + "source": "https://github.com/codeigniter4/queue/tree/develop" + }, + "time": "2026-02-15T08:22:24+00:00" + }, + { + "name": "codeigniter4/settings", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/codeigniter4/settings.git", + "reference": "2748f2b4572d44a940f98c31847d65272cac5666" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/codeigniter4/settings/zipball/2748f2b4572d44a940f98c31847d65272cac5666", + "reference": "2748f2b4572d44a940f98c31847d65272cac5666", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "codeigniter4/devkit": "^1.1.2", + "codeigniter4/framework": "^4.2.3", + "rector/rector": "0.18.13" }, "type": "library", "autoload": { "psr-4": { "CodeIgniter\\Settings\\": "src" }, - "exclude-from-classmap": ["**/Database/Migrations/**"] + "exclude-from-classmap": [ + "**/Database/Migrations/**" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Lonnie Ezell", @@ -279,37 +724,221 @@ ], "description": "Settings library for CodeIgniter 4", "homepage": "https://github.com/codeigniter4/settings", - "keywords": ["Settings", "codeigniter", "codeigniter4"], + "keywords": [ + "Settings", + "codeigniter", + "codeigniter4" + ], "support": { "issues": "https://github.com/codeigniter4/settings/issues", - "source": "https://github.com/codeigniter4/settings/tree/v2.1.0" + "source": "https://github.com/codeigniter4/settings/tree/v2.2.0" }, - "time": "2021-11-22T17:30:18+00:00" + "time": "2024-01-06T07:10:58+00:00" }, { - "name": "composer/ca-bundle", - "version": "1.3.4", + "name": "codeigniter4/shield", + "version": "v1.2.0", "source": { "type": "git", - "url": "https://github.com/composer/ca-bundle.git", - "reference": "69098eca243998b53eed7a48d82dedd28b447cd5" + "url": "https://github.com/codeigniter4/shield.git", + "reference": "2d1b2177a914dcd490f54a6706792eacabd9e3e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/69098eca243998b53eed7a48d82dedd28b447cd5", - "reference": "69098eca243998b53eed7a48d82dedd28b447cd5", + "url": "https://api.github.com/repos/codeigniter4/shield/zipball/2d1b2177a914dcd490f54a6706792eacabd9e3e8", + "reference": "2d1b2177a914dcd490f54a6706792eacabd9e3e8", + "shasum": "" + }, + "require": { + "codeigniter4/settings": "^2.1", + "php": "^8.1" + }, + "provide": { + "codeigniter4/authentication-implementation": "1.0" + }, + "require-dev": { + "codeigniter/phpstan-codeigniter": "^1.3", + "codeigniter4/devkit": "^1.3", + "codeigniter4/framework": ">=4.3.5 <4.5.0 || ^4.5.1", + "firebase/php-jwt": "^6.4", + "mikey179/vfsstream": "^1.6.7", + "mockery/mockery": "^1.0", + "phpstan/phpstan-strict-rules": "^2.0" + }, + "suggest": { + "ext-curl": "Required to use the password validation rule via PwnedValidator class.", + "ext-openssl": "Required to use the JWT Authenticator." + }, + "type": "library", + "autoload": { + "psr-4": { + "CodeIgniter\\Shield\\": "src" + }, + "exclude-from-classmap": [ + "**/Database/Migrations/**" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lonnie Ezell", + "email": "lonnieje@gmail.com", + "role": "Developer" + } + ], + "description": "Authentication and Authorization for CodeIgniter 4", + "homepage": "https://github.com/codeigniter4/shield", + "keywords": [ + "Authentication", + "authorization", + "codeigniter", + "codeigniter4" + ], + "support": { + "docs": "https://codeigniter4.github.io/shield/", + "forum": "https://github.com/codeigniter4/shield/discussions", + "issues": "https://github.com/codeigniter4/shield/issues", + "slack": "https://codeigniterchat.slack.com", + "source": "https://github.com/codeigniter4/shield" + }, + "time": "2025-07-14T10:26:03+00:00" + }, + { + "name": "codeigniter4/tasks", + "version": "dev-develop", + "source": { + "type": "git", + "url": "https://github.com/codeigniter4/tasks.git", + "reference": "8d0c0f83d48dd1ac322d30a4c51ff309ab5368e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/codeigniter4/tasks/zipball/8d0c0f83d48dd1ac322d30a4c51ff309ab5368e0", + "reference": "8d0c0f83d48dd1ac322d30a4c51ff309ab5368e0", + "shasum": "" + }, + "require": { + "codeigniter4/queue": "dev-develop", + "codeigniter4/settings": "^2.0", + "ext-json": "*", + "php": "^8.2" + }, + "require-dev": { + "codeigniter4/devkit": "^1.3", + "codeigniter4/framework": "^4.3" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "CodeIgniter\\Tasks\\": "src" + }, + "exclude-from-classmap": [ + "**/Database/Migrations/**" + ] + }, + "autoload-dev": { + "psr-4": { + "Tests\\Support\\": "tests/_support" + } + }, + "scripts": { + "post-update-cmd": [ + "bash admin/setup.sh" + ], + "analyze": [ + "Composer\\Config::disableProcessTimeout", + "phpstan analyze", + "psalm", + "rector process --dry-run" + ], + "sa": [ + "@analyze" + ], + "ci": [ + "Composer\\Config::disableProcessTimeout", + "@cs", + "@deduplicate", + "@inspect", + "@analyze", + "@test" + ], + "cs": [ + "php-cs-fixer fix --ansi --verbose --dry-run --diff" + ], + "cs-fix": [ + "php-cs-fixer fix --ansi --verbose --diff" + ], + "style": [ + "@cs-fix" + ], + "deduplicate": [ + "phpcpd src/ tests/" + ], + "inspect": [ + "deptrac analyze --cache-file=build/deptrac.cache" + ], + "mutate": [ + "infection --threads=2 --skip-initial-tests --coverage=build/phpunit" + ], + "retool": [ + "retool" + ], + "test": [ + "phpunit" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lonnie Ezell", + "email": "lonnieje@gmail.com", + "role": "Developer" + } + ], + "description": "Task Scheduler for CodeIgniter 4", + "homepage": "https://github.com/codeigniter4/tasks", + "keywords": [ + "codeigniter", + "codeigniter4", + "cron", + "task scheduling" + ], + "support": { + "source": "https://github.com/codeigniter4/tasks/tree/develop", + "issues": "https://github.com/codeigniter4/tasks/issues" + }, + "time": "2026-02-15T08:22:03+00:00" + }, + { + "name": "composer/ca-bundle", + "version": "1.5.10", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/961a5e4056dd2e4a2eedcac7576075947c28bf63", + "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.55", - "psr/log": "^1.0", - "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0" + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { @@ -323,7 +952,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Jordi Boggiano", @@ -332,11 +963,17 @@ } ], "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", - "keywords": ["cabundle", "cacert", "certificate", "ssl", "tls"], + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.3.4" + "source": "https://github.com/composer/ca-bundle/tree/1.5.10" }, "funding": [ { @@ -346,26 +983,22 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2022-10-12T12:08:29+00:00" + "time": "2025-12-08T15:06:51+00:00" }, { "name": "dflydev/dot-access-data", - "version": "v3.0.1", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c" + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c", - "reference": "0992cc19268b259a39e86f296da5f0677841f42c", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", "shasum": "" }, "require": { @@ -376,7 +1009,7 @@ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", "scrutinizer/ocular": "1.6.0", "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^3.14" + "vimeo/psalm": "^4.0.0" }, "type": "library", "extra": { @@ -390,7 +1023,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Dragonfly Development Inc.", @@ -415,212 +1050,43 @@ ], "description": "Given a deep data structure, access data by dot notation.", "homepage": "https://github.com/dflydev/dflydev-dot-access-data", - "keywords": ["access", "data", "dot", "notation"], + "keywords": [ + "access", + "data", + "dot", + "notation" + ], "support": { "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", - "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1" + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" }, - "time": "2021-08-13T13:06:58+00:00" - }, - { - "name": "essence/dom", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/essence/dom.git", - "reference": "e5776d2286f4ccbd048d160c28ac77ccc6d68f3a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/essence/dom/zipball/e5776d2286f4ccbd048d160c28ac77ccc6d68f3a", - "reference": "e5776d2286f4ccbd048d160c28ac77ccc6d68f3a", - "shasum": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Essence\\Dom\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], - "authors": [ - { - "name": "Félix Girault", - "email": "felix.girault@gmail.com", - "homepage": "http://www.felix-girault.fr", - "role": "Developer" - } - ], - "description": "Essence's DOM parser.", - "homepage": "http://github.com/essence/dom", - "keywords": ["dom", "parser"], - "support": { - "issues": "https://github.com/essence/dom/issues", - "source": "https://github.com/essence/dom/tree/1.0.0" - }, - "time": "2015-07-23T20:33:17+00:00" - }, - { - "name": "essence/essence", - "version": "3.5.4", - "source": { - "type": "git", - "url": "https://github.com/essence/essence.git", - "reference": "81e889a87603840dadd04b317a51487df1d45933" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/essence/essence/zipball/81e889a87603840dadd04b317a51487df1d45933", - "reference": "81e889a87603840dadd04b317a51487df1d45933", - "shasum": "" - }, - "require": { - "essence/dom": "~1.0.0", - "essence/http": "~1.0.0", - "fg/parkour": "~1.1.0", - "php": ">=5.5.0" - }, - "suggest": { - "ext-curl": "*" - }, - "type": "library", - "autoload": { - "psr-4": { - "Essence\\": "lib/Essence" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-2-Clause"], - "authors": [ - { - "name": "Félix Girault", - "email": "felix.girault@gmail.com", - "homepage": "http://www.felix-girault.fr", - "role": "Developer" - } - ], - "description": "Extracts information about medias on the web, like youtube videos, twitter statuses or blog articles.", - "homepage": "http://github.com/essence/essence", - "keywords": ["embed", "media", "oembed", "opengraph"], - "support": { - "issues": "https://github.com/essence/essence/issues", - "source": "https://github.com/essence/essence/tree/3.5.4" - }, - "time": "2021-01-21T09:58:10+00:00" - }, - { - "name": "essence/http", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/essence/http.git", - "reference": "ce0e52e0c0f2ed894ce2922ab2fd598dcaac91d2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/essence/http/zipball/ce0e52e0c0f2ed894ce2922ab2fd598dcaac91d2", - "reference": "ce0e52e0c0f2ed894ce2922ab2fd598dcaac91d2", - "shasum": "" - }, - "suggest": { - "ext-curl": "*" - }, - "type": "library", - "autoload": { - "psr-4": { - "Essence\\Http\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], - "authors": [ - { - "name": "Félix Girault", - "email": "felix.girault@gmail.com", - "homepage": "http://www.felix-girault.fr", - "role": "Developer" - } - ], - "description": "Essence's HTTP client.", - "homepage": "http://github.com/essence/http", - "keywords": ["client", "http"], - "support": { - "issues": "https://github.com/essence/http/issues", - "source": "https://github.com/essence/http/tree/1.0.0" - }, - "time": "2015-07-23T20:33:50+00:00" - }, - { - "name": "fg/parkour", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/felixgirault/parkour.git", - "reference": "f837eb640fc4aac81b11fe50d2fa04fb4ec71496" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/felixgirault/parkour/zipball/f837eb640fc4aac81b11fe50d2fa04fb4ec71496", - "reference": "f837eb640fc4aac81b11fe50d2fa04fb4ec71496", - "shasum": "" - }, - "require": { - "php": ">=5.5.0" - }, - "require-dev": { - "phpunit/phpunit": "4.3.*" - }, - "type": "library", - "autoload": { - "psr-4": { - "Parkour\\": "lib" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-2-Clause"], - "authors": [ - { - "name": "Félix Girault", - "email": "felix.girault@gmail.com", - "homepage": "http://www.felix-girault.fr", - "role": "Developer" - } - ], - "description": "A collection of utilities to manipulate arrays.", - "homepage": "http://github.com/felixgirault/parkour", - "keywords": ["array", "manipulation", "traversing"], - "support": { - "issues": "https://github.com/felixgirault/parkour/issues", - "source": "https://github.com/felixgirault/parkour/tree/1.1.1" - }, - "time": "2015-10-03T10:39:22+00:00" + "time": "2024-07-08T12:26:09+00:00" }, { "name": "geoip2/geoip2", - "version": "v2.13.0", + "version": "v3.3.0", "source": { "type": "git", - "url": "git@github.com:maxmind/GeoIP2-php.git", - "reference": "6a41d8fbd6b90052bc34dff3b4252d0f88067b23" + "url": "https://github.com/maxmind/GeoIP2-php.git", + "reference": "49fceddd694295e76e970a32848e03bb19e56b42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/6a41d8fbd6b90052bc34dff3b4252d0f88067b23", - "reference": "6a41d8fbd6b90052bc34dff3b4252d0f88067b23", + "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/49fceddd694295e76e970a32848e03bb19e56b42", + "reference": "49fceddd694295e76e970a32848e03bb19e56b42", "shasum": "" }, "require": { "ext-json": "*", - "maxmind-db/reader": "~1.8", - "maxmind/web-service-common": "~0.8", - "php": ">=7.2" + "maxmind-db/reader": "^1.13.0", + "maxmind/web-service-common": "~0.11", + "php": ">=8.1" }, "require-dev": { "friendsofphp/php-cs-fixer": "3.*", "phpstan/phpstan": "*", - "phpunit/phpunit": "^8.0 || ^9.0", - "squizlabs/php_codesniffer": "3.*" + "phpunit/phpunit": "^10.0", + "squizlabs/php_codesniffer": "4.*" }, "type": "library", "autoload": { @@ -629,7 +1095,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["Apache-2.0"], + "license": [ + "Apache-2.0" + ], "authors": [ { "name": "Gregory J. Oschwald", @@ -639,29 +1107,39 @@ ], "description": "MaxMind GeoIP2 PHP API", "homepage": "https://github.com/maxmind/GeoIP2-php", - "keywords": ["IP", "geoip", "geoip2", "geolocation", "maxmind"], - "time": "2022-08-05T20:32:58+00:00" + "keywords": [ + "IP", + "geoip", + "geoip2", + "geolocation", + "maxmind" + ], + "support": { + "issues": "https://github.com/maxmind/GeoIP2-php/issues", + "source": "https://github.com/maxmind/GeoIP2-php/tree/v3.3.0" + }, + "time": "2025-11-20T18:50:15+00:00" }, { "name": "graham-campbell/result-type", - "version": "v1.1.0", + "version": "v1.1.4", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8" + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/a878d45c1914464426dc94da61c9e1d36ae262a8", - "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9" + "phpoption/phpoption": "^1.9.5" }, "require-dev": { - "phpunit/phpunit": "^8.5.28 || ^9.5.21" + "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" }, "type": "library", "autoload": { @@ -670,7 +1148,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Graham Campbell", @@ -688,7 +1168,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.0" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" }, "funding": [ { @@ -700,20 +1180,345 @@ "type": "tidelift" } ], - "time": "2022-07-30T15:56:11+00:00" + "time": "2025-12-27T19:43:20+00:00" }, { - "name": "james-heinrich/getid3", - "version": "2.0.x-dev", + "name": "guzzlehttp/guzzle", + "version": "7.10.0", "source": { "type": "git", - "url": "https://github.com/JamesHeinrich/getID3.git", - "reference": "ee238d552571c6029898b087d5fc95df826418d6" + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JamesHeinrich/getID3/zipball/ee238d552571c6029898b087d5fc95df826418d6", - "reference": "ee238d552571c6029898b087d5fc95df826418d6", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2025-08-23T22:36:01+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "481557b130ef3790cf82b713667b43030dc9c957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.3.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2025-08-22T14:34:08+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "21dc724a0583619cd1652f673303492272778051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.8.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2025-08-23T21:21:41+00:00" + }, + { + "name": "james-heinrich/getid3", + "version": "v2.0.0-beta6", + "source": { + "type": "git", + "url": "https://github.com/JamesHeinrich/getID3.git", + "reference": "8bf46222ae008d870e6676b4bf455ed664d90d05" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JamesHeinrich/getID3/zipball/8bf46222ae008d870e6676b4bf455ed664d90d05", + "reference": "8bf46222ae008d870e6676b4bf455ed664d90d05", "shasum": "" }, "require": { @@ -752,7 +1557,11 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["GPL-1.0-or-later", "LGPL-3.0-only", "MPL-2.0"], + "license": [ + "GPL-1.0-or-later", + "LGPL-3.0-only", + "MPL-2.0" + ], "authors": [ { "name": "James Heinrich", @@ -769,99 +1578,48 @@ ], "description": "Extract and write useful information to/from popular multimedia file formats", "homepage": "https://www.getid3.org/", - "keywords": ["audio", "codecs", "id3", "metadata", "tags", "video"], + "keywords": [ + "audio", + "codecs", + "id3", + "metadata", + "tags", + "video" + ], "support": { "issues": "https://github.com/JamesHeinrich/getID3/issues", - "source": "https://github.com/JamesHeinrich/getID3/tree/2.0" + "source": "https://github.com/JamesHeinrich/getID3/tree/v2.0.0-beta6" }, - "time": "2021-12-15T17:29:14+00:00" - }, - { - "name": "kint-php/kint", - "version": "4.2.3", - "source": { - "type": "git", - "url": "https://github.com/kint-php/kint.git", - "reference": "7601bfd95ccc50a1b903c2764b31d00919e8edd9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/kint-php/kint/zipball/7601bfd95ccc50a1b903c2764b31d00919e8edd9", - "reference": "7601bfd95ccc50a1b903c2764b31d00919e8edd9", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.0", - "phpspec/prophecy-phpunit": "^2", - "phpunit/phpunit": "^9.0", - "seld/phar-utils": "^1.0", - "symfony/finder": "^3.0 || ^4.0 || ^5.0", - "vimeo/psalm": "^4.0" - }, - "suggest": { - "kint-php/kint-helpers": "Provides extra helper functions", - "kint-php/kint-twig": "Provides d() and s() functions in twig templates" - }, - "type": "library", - "autoload": { - "files": ["init.php"], - "psr-4": { - "Kint\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], - "authors": [ - { - "name": "Jonathan Vollebregt", - "homepage": "https://github.com/jnvsor" - }, - { - "name": "Contributors", - "homepage": "https://github.com/kint-php/kint/graphs/contributors" - } - ], - "description": "Kint - debugging tool for PHP developers", - "homepage": "https://kint-php.github.io/kint/", - "keywords": ["debug", "kint", "php"], - "support": { - "issues": "https://github.com/kint-php/kint/issues", - "source": "https://github.com/kint-php/kint/tree/4.2.3" - }, - "time": "2022-10-01T20:16:33+00:00" + "time": "2023-11-02T19:40:57+00:00" }, { "name": "laminas/laminas-escaper", - "version": "2.12.0", + "version": "2.18.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-escaper.git", - "reference": "ee7a4c37bf3d0e8c03635d5bddb5bb3184ead490" + "reference": "06f211dfffff18d91844c1f55250d5d13c007e18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/ee7a4c37bf3d0e8c03635d5bddb5bb3184ead490", - "reference": "ee7a4c37bf3d0e8c03635d5bddb5bb3184ead490", + "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/06f211dfffff18d91844c1f55250d5d13c007e18", + "reference": "06f211dfffff18d91844c1f55250d5d13c007e18", "shasum": "" }, "require": { "ext-ctype": "*", "ext-mbstring": "*", - "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0" + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "conflict": { "zendframework/zend-escaper": "*" }, "require-dev": { - "infection/infection": "^0.26.6", - "laminas/laminas-coding-standard": "~2.4.0", - "maglnet/composer-require-checker": "^3.8.0", - "phpunit/phpunit": "^9.5.18", - "psalm/plugin-phpunit": "^0.17.0", - "vimeo/psalm": "^4.22.0" + "infection/infection": "^0.31.0", + "laminas/laminas-coding-standard": "~3.1.0", + "phpunit/phpunit": "^11.5.42", + "psalm/plugin-phpunit": "^0.19.5", + "vimeo/psalm": "^6.13.1" }, "type": "library", "autoload": { @@ -870,10 +1628,15 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", "homepage": "https://laminas.dev", - "keywords": ["escaper", "laminas"], + "keywords": [ + "escaper", + "laminas" + ], "support": { "chat": "https://laminas.dev/chat", "docs": "https://docs.laminas.dev/laminas-escaper/", @@ -888,20 +1651,20 @@ "type": "community_bridge" } ], - "time": "2022-10-10T10:11:09+00:00" + "time": "2025-10-14T18:31:13+00:00" }, { "name": "league/commonmark", - "version": "2.3.5", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "84d74485fdb7074f4f9dd6f02ab957b1de513257" + "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/84d74485fdb7074f4f9dd6f02ab957b1de513257", - "reference": "84d74485fdb7074f4f9dd6f02ab957b1de513257", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/4efa10c1e56488e658d10adf7b7b7dcd19940bfb", + "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb", "shasum": "" }, "require": { @@ -914,22 +1677,23 @@ }, "require-dev": { "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.0", - "commonmark/commonmark.js": "0.30.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", "composer/package-versions-deprecated": "^1.8", "embed/embed": "^4.4", "erusev/parsedown": "^1.0", "ext-json": "*", "github/gfm": "0.29.0", - "michelf/php-markdown": "^1.4", + "michelf/php-markdown": "^1.4 || ^2.0", "nyholm/psr7": "^1.5", "phpstan/phpstan": "^1.8.2", - "phpunit/phpunit": "^9.5.21", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3 | ^6.0", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "symfony/finder": "^5.3 | ^6.0 | ^7.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", "unleashedtech/php-coding-standard": "^3.1.1", - "vimeo/psalm": "^4.24.0" + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" }, "suggest": { "symfony/yaml": "v2.3+ required if using the Front Matter extension" @@ -937,7 +1701,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "2.9-dev" } }, "autoload": { @@ -946,7 +1710,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Colin O'Dell", @@ -992,20 +1758,20 @@ "type": "tidelift" } ], - "time": "2022-07-29T10:59:45+00:00" + "time": "2025-11-26T21:48:24+00:00" }, { "name": "league/config", - "version": "v1.1.1", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/thephpleague/config.git", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", "shasum": "" }, "require": { @@ -1014,7 +1780,7 @@ "php": "^7.4 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.90", + "phpstan/phpstan": "^1.8.2", "phpunit/phpunit": "^9.5.5", "scrutinizer/ocular": "^1.8.1", "unleashedtech/php-coding-standard": "^3.1", @@ -1032,7 +1798,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Colin O'Dell", @@ -1072,20 +1840,20 @@ "type": "github" } ], - "time": "2021-08-14T12:15:32+00:00" + "time": "2022-12-11T20:36:23+00:00" }, { "name": "league/html-to-markdown", - "version": "5.1.0", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/thephpleague/html-to-markdown.git", - "reference": "e0fc8cf07bdabbcd3765341ecb50c34c271d64e1" + "reference": "0b4066eede55c48f38bcee4fb8f0aa85654390fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/e0fc8cf07bdabbcd3765341ecb50c34c271d64e1", - "reference": "e0fc8cf07bdabbcd3765341ecb50c34c271d64e1", + "url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/0b4066eede55c48f38bcee4fb8f0aa85654390fd", + "reference": "0b4066eede55c48f38bcee4fb8f0aa85654390fd", "shasum": "" }, "require": { @@ -1095,13 +1863,15 @@ }, "require-dev": { "mikehaertl/php-shellcommand": "^1.1.0", - "phpstan/phpstan": "^0.12.99", + "phpstan/phpstan": "^1.8.8", "phpunit/phpunit": "^8.5 || ^9.2", "scrutinizer/ocular": "^1.6", - "unleashedtech/php-coding-standard": "^2.7", - "vimeo/psalm": "^4.22" + "unleashedtech/php-coding-standard": "^2.7 || ^3.0", + "vimeo/psalm": "^4.22 || ^5.0" }, - "bin": ["bin/html-to-markdown"], + "bin": [ + "bin/html-to-markdown" + ], "type": "library", "extra": { "branch-alias": { @@ -1114,7 +1884,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Colin O'Dell", @@ -1131,10 +1903,13 @@ ], "description": "An HTML-to-markdown conversion helper for PHP", "homepage": "https://github.com/thephpleague/html-to-markdown", - "keywords": ["html", "markdown"], + "keywords": [ + "html", + "markdown" + ], "support": { "issues": "https://github.com/thephpleague/html-to-markdown/issues", - "source": "https://github.com/thephpleague/html-to-markdown/tree/5.1.0" + "source": "https://github.com/thephpleague/html-to-markdown/tree/5.1.1" }, "funding": [ { @@ -1154,40 +1929,39 @@ "type": "tidelift" } ], - "time": "2022-03-02T17:24:08+00:00" + "time": "2023-07-12T21:21:09+00:00" }, { "name": "maxmind-db/reader", - "version": "v1.11.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git", - "reference": "b1f3c0699525336d09cc5161a2861268d9f2ae5b" + "reference": "2194f58d0f024ce923e685cdf92af3daf9951908" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/b1f3c0699525336d09cc5161a2861268d9f2ae5b", - "reference": "b1f3c0699525336d09cc5161a2861268d9f2ae5b", + "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/2194f58d0f024ce923e685cdf92af3daf9951908", + "reference": "2194f58d0f024ce923e685cdf92af3daf9951908", "shasum": "" }, "require": { "php": ">=7.2" }, "conflict": { - "ext-maxminddb": "<1.10.1,>=2.0.0" + "ext-maxminddb": "<1.11.1 || >=2.0.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "3.*", - "php-coveralls/php-coveralls": "^2.1", "phpstan/phpstan": "*", - "phpunit/phpcov": ">=6.0.0", "phpunit/phpunit": ">=8.0.0,<10.0.0", - "squizlabs/php_codesniffer": "3.*" + "squizlabs/php_codesniffer": "4.*" }, "suggest": { "ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", "ext-gmp": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", - "ext-maxminddb": "A C-based database decoder that provides significantly faster lookups" + "ext-maxminddb": "A C-based database decoder that provides significantly faster lookups", + "maxmind-db/reader-ext": "C extension for significantly faster IP lookups (install via PIE: pie install maxmind-db/reader-ext)" }, "type": "library", "autoload": { @@ -1196,7 +1970,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["Apache-2.0"], + "license": [ + "Apache-2.0" + ], "authors": [ { "name": "Gregory J. Oschwald", @@ -1206,38 +1982,44 @@ ], "description": "MaxMind DB Reader API", "homepage": "https://github.com/maxmind/MaxMind-DB-Reader-php", - "keywords": ["database", "geoip", "geoip2", "geolocation", "maxmind"], + "keywords": [ + "database", + "geoip", + "geoip2", + "geolocation", + "maxmind" + ], "support": { "issues": "https://github.com/maxmind/MaxMind-DB-Reader-php/issues", - "source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.11.0" + "source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.13.1" }, - "time": "2021-10-18T15:23:10+00:00" + "time": "2025-11-21T22:24:26+00:00" }, { "name": "maxmind/web-service-common", - "version": "v0.9.0", + "version": "v0.11.1", "source": { "type": "git", "url": "https://github.com/maxmind/web-service-common-php.git", - "reference": "4dc5a3e8df38aea4ca3b1096cee3a038094e9b53" + "reference": "c309236b5a5555b96cf560089ec3cead12d845d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/4dc5a3e8df38aea4ca3b1096cee3a038094e9b53", - "reference": "4dc5a3e8df38aea4ca3b1096cee3a038094e9b53", + "url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/c309236b5a5555b96cf560089ec3cead12d845d2", + "reference": "c309236b5a5555b96cf560089ec3cead12d845d2", "shasum": "" }, "require": { "composer/ca-bundle": "^1.0.3", "ext-curl": "*", "ext-json": "*", - "php": ">=7.2" + "php": ">=8.1" }, "require-dev": { "friendsofphp/php-cs-fixer": "3.*", "phpstan/phpstan": "*", - "phpunit/phpunit": "^8.0 || ^9.0", - "squizlabs/php_codesniffer": "3.*" + "phpunit/phpunit": "^10.0", + "squizlabs/php_codesniffer": "4.*" }, "type": "library", "autoload": { @@ -1247,7 +2029,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["Apache-2.0"], + "license": [ + "Apache-2.0" + ], "authors": [ { "name": "Gregory Oschwald", @@ -1258,32 +2042,32 @@ "homepage": "https://github.com/maxmind/web-service-common-php", "support": { "issues": "https://github.com/maxmind/web-service-common-php/issues", - "source": "https://github.com/maxmind/web-service-common-php/tree/v0.9.0" + "source": "https://github.com/maxmind/web-service-common-php/tree/v0.11.1" }, - "time": "2022-03-28T17:43:20+00:00" + "time": "2026-01-13T17:56:03+00:00" }, { "name": "melbahja/seo", - "version": "v2.1.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/melbahja/seo.git", - "reference": "22b0b3273bf9c8867cadf018e4daa3e426525929" + "reference": "e8d36b2c46e1b05af957c90beea19090f64d5bf9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/melbahja/seo/zipball/22b0b3273bf9c8867cadf018e4daa3e426525929", - "reference": "22b0b3273bf9c8867cadf018e4daa3e426525929", + "url": "https://api.github.com/repos/melbahja/seo/zipball/e8d36b2c46e1b05af957c90beea19090f64d5bf9", + "reference": "e8d36b2c46e1b05af957c90beea19090f64d5bf9", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", "ext-xml": "*", - "php": ">=7.2" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^10.0" }, "type": "library", "autoload": { @@ -1292,7 +2076,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Mohamed ELbahja", @@ -1301,48 +2087,53 @@ "role": "Developer" } ], - "description": "Simple PHP library to help developers 🍻 do better on-page SEO optimization", + "description": "SEO library for PHP is a simple PHP library to help developers 🍻 do better on-page SEO optimizations.", "keywords": [ - "PHP7", + "images sitemaps", + "index sitemaps", "meta tags", + "news sitemaps", "open graph", + "php8", + "rich results", "schema.org", "search engine optimization", "seo", "sitemap index", "sitemap.xml", "sitemaps", - "twitter tags" + "twitter tags", + "video sitemaps" ], "support": { "issues": "https://github.com/melbahja/seo/issues", - "source": "https://github.com/melbahja/seo/tree/v2.1.1" + "source": "https://github.com/melbahja/seo/tree/v3.0.2" }, - "time": "2022-09-11T11:16:07+00:00" + "time": "2026-02-08T12:48:54+00:00" }, { "name": "michalsn/codeigniter4-uuid", - "version": "dev-develop", + "version": "v1.3.1", "source": { "type": "git", "url": "https://github.com/michalsn/codeigniter4-uuid.git", - "reference": "b26512ac4f3f0c772fbfa2c3317346d3c17e2d44" + "reference": "31457ec91f54e3c981762d9f06e87e417869c380" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/michalsn/codeigniter4-uuid/zipball/b26512ac4f3f0c772fbfa2c3317346d3c17e2d44", - "reference": "b26512ac4f3f0c772fbfa2c3317346d3c17e2d44", + "url": "https://api.github.com/repos/michalsn/codeigniter4-uuid/zipball/31457ec91f54e3c981762d9f06e87e417869c380", + "reference": "31457ec91f54e3c981762d9f06e87e417869c380", "shasum": "" }, "require": { - "php": ">=7.3", - "ramsey/uuid": "^4.0" + "ext-ctype": "*", + "php": ">=8.1", + "ramsey/uuid": "^4.7" }, "require-dev": { "codeigniter4/codeigniter4": "dev-develop", "phpunit/phpunit": "8.5.*" }, - "default-branch": true, "type": "library", "autoload": { "psr-4": { @@ -1350,7 +2141,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "michalsn", @@ -1360,114 +2153,198 @@ ], "description": "UUID package for CodeIgniter 4 with support for Model and Entity.", "homepage": "https://github.com/michalsn/codeigniter4-uuid", - "keywords": ["codeigniter4", "entity", "model", "uuid"], + "keywords": [ + "codeigniter4", + "entity", + "model", + "uuid" + ], "support": { "issues": "https://github.com/michalsn/codeigniter4-uuid/issues", - "source": "https://github.com/michalsn/codeigniter4-uuid/tree/develop" + "source": "https://github.com/michalsn/codeigniter4-uuid/tree/v1.3.1" }, - "time": "2021-05-10T16:28:01+00:00" + "time": "2025-10-16T10:20:23+00:00" }, { - "name": "myth/auth", - "version": "dev-develop", + "name": "mpratt/embera", + "version": "2.0.42", "source": { "type": "git", - "url": "https://github.com/lonnieezell/myth-auth.git", - "reference": "cc94231f5284e9578967aba4796f018809669c84" + "url": "https://github.com/mpratt/Embera.git", + "reference": "afa728339c6f078c803c9277a5054ca241b3c469" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lonnieezell/myth-auth/zipball/cc94231f5284e9578967aba4796f018809669c84", - "reference": "cc94231f5284e9578967aba4796f018809669c84", + "url": "https://api.github.com/repos/mpratt/Embera/zipball/afa728339c6f078c803c9277a5054ca241b3c469", + "reference": "afa728339c6f078c803c9277a5054ca241b3c469", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0" - }, - "provide": { - "codeigniter4/authentication-implementation": "1.0" + "ext-json": "*", + "php": ">=5.6" }, "require-dev": { - "codeigniter4/codeigniter4-standard": "^1.0", - "codeigniter4/devkit": "^1.0", - "codeigniter4/framework": "^4.1", - "mockery/mockery": "^1.0" + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9.0||^10.0", + "symfony/yaml": "^2.1" + }, + "suggest": { + "ext-curl": "Fetch data using curl instead of using file_get_contents" }, - "default-branch": true, "type": "library", "autoload": { "psr-4": { - "Myth\\Auth\\": "src" - }, - "exclude-from-classmap": ["**/Database/Migrations/**"] + "Embera\\": "src/Embera" + } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { - "name": "Lonnie Ezell", - "email": "lonnieje@gmail.com", - "homepage": "http://newmythmedia.com", - "role": "Developer" + "name": "Michael Pratt", + "email": "yo@michael-pratt.com", + "homepage": "http://www.michael-pratt.com", + "role": "Author/Developer" } ], - "description": "Flexible authentication/authorization system for CodeIgniter 4.", - "homepage": "https://github.com/lonnieezell/myth-auth", - "keywords": ["Authentication", "authorization", "codeigniter"], + "description": "Oembed consumer library. Converts urls into their html embed code. Supports 150+ sites, such as Youtube, Twitter, vimeo, Instagram etc.", + "homepage": "https://github.com/mpratt/Embera", + "keywords": [ + "Auto embed", + "Embed Text", + "Responsive Embeds", + "Url Embed", + "embed", + "instagram", + "oembed", + "twitter", + "vimeo", + "vine", + "youtube" + ], "support": { - "issues": "https://github.com/lonnieezell/myth-auth/issues", - "source": "https://github.com/lonnieezell/myth-auth/tree/develop" + "issues": "https://github.com/mpratt/Embera/issues", + "source": "https://github.com/mpratt/Embera/tree/2.0.42" }, "funding": [ { - "url": "https://github.com/lonnieezell", - "type": "github" - }, - { - "url": "https://github.com/mgatner", - "type": "github" - }, - { - "url": "https://www.patreon.com/lonnieezell", - "type": "patreon" + "url": "https://paypal.me/mtpratt", + "type": "paypal" } ], - "time": "2022-08-01T17:23:52+00:00" + "time": "2025-01-04T06:07:59+00:00" }, { - "name": "nette/schema", - "version": "v1.2.2", + "name": "mtdowling/jmespath.php", + "version": "2.8.0", "source": { "type": "git", - "url": "https://github.com/nette/schema.git", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", - "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a2a865e05d5f420b50cc2f85bb78d565db12a6bc", + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc", "shasum": "" }, "require": { - "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": ">=7.1 <8.2" + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" }, "require-dev": { - "nette/tester": "^2.3 || ^2.4", - "phpstan/phpstan-nette": "^0.12", - "tracy/tracy": "^2.7" + "composer/xdebug-handler": "^3.0.3", + "phpunit/phpunit": "^8.5.33" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "files": [ + "src/JmesPath.php" + ], + "psr-4": { + "JmesPath\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "support": { + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/2.8.0" + }, + "time": "2024-09-04T18:46:31+00:00" + }, + { + "name": "nette/schema", + "version": "v1.3.4", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/086497a2f34b82fede9b5a41cc8e131d087cd8f7", + "reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7", + "shasum": "" + }, + "require": { + "nette/utils": "^4.0", + "php": "8.1 - 8.5" + }, + "require-dev": { + "nette/tester": "^2.6", + "phpstan/phpstan": "^2.0@stable", + "tracy/tracy": "^2.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { - "classmap": ["src/"] + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"], + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], "authors": [ { "name": "David Grudl", @@ -1480,37 +2357,44 @@ ], "description": "📐 Nette Schema: validating data structures against a given Schema.", "homepage": "https://nette.org", - "keywords": ["config", "nette"], + "keywords": [ + "config", + "nette" + ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.2" + "source": "https://github.com/nette/schema/tree/v1.3.4" }, - "time": "2021-10-15T11:40:02+00:00" + "time": "2026-02-08T02:54:00+00:00" }, { "name": "nette/utils", - "version": "v3.2.8", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "02a54c4c872b99e4ec05c4aec54b5a06eb0f6368" + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/02a54c4c872b99e4ec05c4aec54b5a06eb0f6368", - "reference": "02a54c4c872b99e4ec05c4aec54b5a06eb0f6368", + "url": "https://api.github.com/repos/nette/utils/zipball/bb3ea637e3d131d72acc033cfc2746ee893349fe", + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe", "shasum": "" }, "require": { - "php": ">=7.2 <8.3" + "php": "8.2 - 8.5" }, "conflict": { - "nette/di": "<3.0.6" + "nette/finder": "<3", + "nette/schema": "<1.2.2" }, "require-dev": { - "nette/tester": "~2.0", - "phpstan/phpstan": "^1.0", - "tracy/tracy": "^2.3" + "jetbrains/phpstorm-attributes": "^1.2", + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.5", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1@stable", + "tracy/tracy": "^2.9" }, "suggest": { "ext-gd": "to use Image", @@ -1518,20 +2402,28 @@ "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", "ext-json": "to use Nette\\Utils\\Json", "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", - "ext-xml": "to use Strings::length() etc. when mbstring is not available" + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "4.1-dev" } }, "autoload": { - "classmap": ["src/"] + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"], + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], "authors": [ { "name": "David Grudl", @@ -1562,72 +2454,76 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.8" + "source": "https://github.com/nette/utils/tree/v4.1.3" }, - "time": "2022-09-12T23:36:20+00:00" + "time": "2026-02-13T03:05:33+00:00" }, { - "name": "opawg/user-agents-php", - "version": "v1.0", + "name": "opawg/user-agents-v2-php", + "version": "dev-main", "source": { "type": "git", - "url": "https://github.com/opawg/user-agents-php.git", - "reference": "e22c7be05f475b44d0e6ecd76acf1617a2efef85" + "url": "https://github.com/opawg/user-agents-v2-php.git", + "reference": "1b7646bc6e82501c99466fcdef23700604966b97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opawg/user-agents-php/zipball/e22c7be05f475b44d0e6ecd76acf1617a2efef85", - "reference": "e22c7be05f475b44d0e6ecd76acf1617a2efef85", + "url": "https://api.github.com/repos/opawg/user-agents-v2-php/zipball/1b7646bc6e82501c99466fcdef23700604966b97", + "reference": "1b7646bc6e82501c99466fcdef23700604966b97", "shasum": "" }, + "default-branch": true, "type": "library", "autoload": { "psr-4": { - "Opawg\\UserAgentsPhp\\": "src/" + "Opawg\\UserAgentsV2Php\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Benjamin Bellamy", - "email": "ben@podlibre.org", - "homepage": "https://podlibre.org/" + "email": "benjamin@castopod.org", + "homepage": "https://castopod.org/" } ], - "description": "PHP implementation for opawg/user-agents.", - "homepage": "https://github.com/opawg/user-agents-php", + "description": "PHP implementation for opawg/user-agents-v2.", + "homepage": "https://github.com/opawg/user-agents-v2-php", "support": { - "source": "https://github.com/opawg/user-agents-php/tree/v1.0" + "issues": "https://github.com/opawg/user-agents-v2-php/issues", + "source": "https://github.com/opawg/user-agents-v2-php/tree/main" }, - "time": "2020-11-28T10:54:05+00:00" + "time": "2023-12-20T16:54:44+00:00" }, { "name": "phpoption/phpoption", - "version": "1.9.0", + "version": "1.9.5", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab" + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", - "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8", - "phpunit/phpunit": "^8.5.28 || ^9.5.21" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" }, "type": "library", "extra": { "bamarni-bin": { "bin-links": true, - "forward-command": true + "forward-command": false }, "branch-alias": { "dev-master": "1.9-dev" @@ -1639,7 +2535,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["Apache-2.0"], + "license": [ + "Apache-2.0" + ], "authors": [ { "name": "Johannes M. Schmitt", @@ -1653,10 +2551,15 @@ } ], "description": "Option Type for PHP", - "keywords": ["language", "option", "php", "type"], + "keywords": [ + "language", + "option", + "php", + "type" + ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.0" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" }, "funding": [ { @@ -1668,20 +2571,20 @@ "type": "tidelift" } ], - "time": "2022-07-30T15:51:26+00:00" + "time": "2025-12-27T19:41:33+00:00" }, { "name": "phpseclib/phpseclib", - "version": "2.0.38", + "version": "2.0.51", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "b03536539f43a4f9aa33c4f0b2f3a1c752088fcd" + "reference": "ed661e7cdaeb8c419e609e2f3203551a13c2ed48" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/b03536539f43a4f9aa33c4f0b2f3a1c752088fcd", - "reference": "b03536539f43a4f9aa33c4f0b2f3a1c752088fcd", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/ed661e7cdaeb8c419e609e2f3203551a13c2ed48", + "reference": "ed661e7cdaeb8c419e609e2f3203551a13c2ed48", "shasum": "" }, "require": { @@ -1701,13 +2604,17 @@ }, "type": "library", "autoload": { - "files": ["phpseclib/bootstrap.php"], + "files": [ + "phpseclib/bootstrap.php" + ], "psr-4": { "phpseclib\\": "phpseclib/" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Jim Wigginton", @@ -1758,7 +2665,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/2.0.38" + "source": "https://github.com/phpseclib/phpseclib/tree/2.0.51" }, "funding": [ { @@ -1774,7 +2681,7 @@ "type": "tidelift" } ], - "time": "2022-09-02T17:04:26+00:00" + "time": "2026-01-27T09:11:52+00:00" }, { "name": "psr/cache", @@ -1805,7 +2712,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "PHP-FIG", @@ -1813,7 +2722,11 @@ } ], "description": "Common interface for caching libraries", - "keywords": ["cache", "psr", "psr-6"], + "keywords": [ + "cache", + "psr", + "psr-6" + ], "support": { "source": "https://github.com/php-fig/cache/tree/3.0.0" }, @@ -1848,7 +2761,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "PHP-FIG", @@ -1856,7 +2771,11 @@ } ], "description": "Standard interfaces for event handling.", - "keywords": ["events", "psr", "psr-14"], + "keywords": [ + "events", + "psr", + "psr-14" + ], "support": { "issues": "https://github.com/php-fig/event-dispatcher/issues", "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" @@ -1864,35 +2783,197 @@ "time": "2019-01-08T18:20:26+00:00" }, { - "name": "psr/log", - "version": "1.1.4", + "name": "psr/http-client", + "version": "1.0.3", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Http\\Client\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], "authors": [ { "name": "PHP-FIG", @@ -1901,57 +2982,114 @@ ], "description": "Common interface for logging libraries", "homepage": "https://github.com/php-fig/log", - "keywords": ["log", "psr", "psr-3"], + "keywords": [ + "log", + "psr", + "psr-3" + ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-05-03T11:20:27+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { - "name": "ramsey/collection", - "version": "1.2.2", + "name": "ralouphie/getallheaders", + "version": "3.0.3", "source": { "type": "git", - "url": "https://github.com/ramsey/collection.git", - "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a" + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/cccc74ee5e328031b15640b51056ee8d3bb66c0a", - "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", "shasum": "" }, "require": { - "php": "^7.3 || ^8", - "symfony/polyfill-php81": "^1.23" + "php": ">=5.6" }, "require-dev": { - "captainhook/captainhook": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "ergebnis/composer-normalize": "^2.6", - "fakerphp/faker": "^1.5", - "hamcrest/hamcrest-php": "^2", - "jangregor/phpstan-prophecy": "^0.8", - "mockery/mockery": "^1.3", - "phpspec/prophecy-phpunit": "^2.0", - "phpstan/extension-installer": "^1", - "phpstan/phpstan": "^0.12.32", - "phpstan/phpstan-mockery": "^0.12.5", - "phpstan/phpstan-phpunit": "^0.12.11", - "phpunit/phpunit": "^8.5 || ^9", - "psy/psysh": "^0.10.4", - "slevomat/coding-standard": "^6.3", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.4" + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" }, "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, "autoload": { "psr-4": { "Ramsey\\Collection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Ben Ramsey", @@ -1960,72 +3098,63 @@ } ], "description": "A PHP library for representing and manipulating collections.", - "keywords": ["array", "collection", "hash", "map", "queue", "set"], + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/1.2.2" + "source": "https://github.com/ramsey/collection/tree/2.1.1" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", - "type": "tidelift" - } - ], - "time": "2021-10-10T03:01:02+00:00" + "time": "2025-03-22T05:38:12+00:00" }, { "name": "ramsey/uuid", - "version": "4.5.1", + "version": "4.9.2", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "a161a26d917604dc6d3aa25100fddf2556e9f35d" + "reference": "8429c78ca35a09f27565311b98101e2826affde0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/a161a26d917604dc6d3aa25100fddf2556e9f35d", - "reference": "a161a26d917604dc6d3aa25100fddf2556e9f35d", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", + "reference": "8429c78ca35a09f27565311b98101e2826affde0", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10", - "ext-ctype": "*", - "ext-json": "*", + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", "php": "^8.0", - "ramsey/collection": "^1.0" + "ramsey/collection": "^1.2 || ^2.0" }, "replace": { "rhumsaa/uuid": "self.version" }, "require-dev": { - "captainhook/captainhook": "^5.10", + "captainhook/captainhook": "^5.25", "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.8", - "ergebnis/composer-normalize": "^2.15", - "mockery/mockery": "^1.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.2", - "php-mock/php-mock-mockery": "^1.3", - "php-parallel-lint/php-parallel-lint": "^1.1", - "phpbench/phpbench": "^1.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^8.5 || ^9", - "ramsey/composer-repl": "^1.4", - "slevomat/coding-standard": "^8.4", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.9" + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" }, "suggest": { "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", - "ext-ctype": "Enables faster processing of character classification using ctype functions.", "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", @@ -2038,43 +3167,41 @@ } }, "autoload": { - "files": ["src/functions.php"], + "files": [ + "src/functions.php" + ], "psr-4": { "Ramsey\\Uuid\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", - "keywords": ["guid", "identifier", "uuid"], + "keywords": [ + "guid", + "identifier", + "uuid" + ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.5.1" + "source": "https://github.com/ramsey/uuid/tree/4.9.2" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", - "type": "tidelift" - } - ], - "time": "2022-09-16T03:22:46+00:00" + "time": "2025-12-14T04:43:48+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.1.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", - "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -2082,19 +3209,23 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.1-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { - "files": ["function.php"] + "files": [ + "function.php" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -2108,7 +3239,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -2124,24 +3255,94 @@ "type": "tidelift" } ], - "time": "2022-02-25T11:15:52+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.26.0", + "name": "symfony/filesystem", + "version": "v8.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + "url": "https://github.com/symfony/filesystem.git", + "reference": "d937d400b980523dc9ee946bb69972b5e619058d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d937d400b980523dc9ee946bb69972b5e619058d", + "reference": "d937d400b980523dc9ee946bb69972b5e619058d", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.4", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v8.0.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-01T09:13:36+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -2151,22 +3352,23 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "files": ["bootstrap.php"], + "files": [ + "bootstrap.php" + ], "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Gert de Pagter", @@ -2179,9 +3381,14 @@ ], "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", - "keywords": ["compatibility", "ctype", "polyfill", "portable"], + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -2192,29 +3399,34 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.26.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-iconv": "*", + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -2224,22 +3436,23 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "files": ["bootstrap.php"], + "files": [ + "bootstrap.php" + ], "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -2252,9 +3465,15 @@ ], "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", - "keywords": ["compatibility", "mbstring", "polyfill", "portable", "shim"], + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -2265,49 +3484,56 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.26.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "files": ["bootstrap.php"], + "files": [ + "bootstrap.php" + ], "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, - "classmap": ["Resources/stubs"] + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Ion Bazan", @@ -2324,9 +3550,14 @@ ], "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", - "keywords": ["compatibility", "polyfill", "portable", "shim"], + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -2338,71 +3569,7 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-10T07:21:04+00:00" - }, - { - "name": "symfony/polyfill-php81", - "version": "v1.26.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1", - "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": ["bootstrap.php"], - "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" - }, - "classmap": ["Resources/stubs"] - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": ["compatibility", "polyfill", "portable", "shim"], - "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/nicolas-grekas", "type": "github" }, { @@ -2410,43 +3577,47 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { "name": "vlucas/phpdotenv", - "version": "v5.4.1", + "version": "v5.6.3", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "264dce589e7ce37a7ba99cb901eed8249fbec92f" + "reference": "955e7815d677a3eaa7075231212f2110983adecc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/264dce589e7ce37a7ba99cb901eed8249fbec92f", - "reference": "264dce589e7ce37a7ba99cb901eed8249fbec92f", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", + "reference": "955e7815d677a3eaa7075231212f2110983adecc", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.0.2", - "php": "^7.1.3 || ^8.0", - "phpoption/phpoption": "^1.8", - "symfony/polyfill-ctype": "^1.23", - "symfony/polyfill-mbstring": "^1.23.1", - "symfony/polyfill-php80": "^1.23.1" + "graham-campbell/result-type": "^1.1.4", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-filter": "*", - "phpunit/phpunit": "^7.5.20 || ^8.5.21 || ^9.5.10" + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" }, "suggest": { "ext-filter": "Required to use the boolean validator." }, "type": "library", "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, "branch-alias": { - "dev-master": "5.4-dev" + "dev-master": "5.6-dev" } }, "autoload": { @@ -2455,7 +3626,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Graham Campbell", @@ -2469,10 +3642,14 @@ } ], "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", - "keywords": ["dotenv", "env", "environment"], + "keywords": [ + "dotenv", + "env", + "environment" + ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.4.1" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3" }, "funding": [ { @@ -2484,20 +3661,20 @@ "type": "tidelift" } ], - "time": "2021-12-12T23:22:04+00:00" + "time": "2025-12-27T19:49:13+00:00" }, { "name": "whichbrowser/parser", - "version": "v2.1.7", + "version": "v2.1.8", "source": { "type": "git", "url": "https://github.com/WhichBrowser/Parser-PHP.git", - "reference": "1044880bc792dbce5948fbff22ae731c43c280d9" + "reference": "581d614d686bfbec3529ad60562a5213ac5d8d72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/WhichBrowser/Parser-PHP/zipball/1044880bc792dbce5948fbff22ae731c43c280d9", - "reference": "1044880bc792dbce5948fbff22ae731c43c280d9", + "url": "https://api.github.com/repos/WhichBrowser/Parser-PHP/zipball/581d614d686bfbec3529ad60562a5213ac5d8d72", + "reference": "581d614d686bfbec3529ad60562a5213ac5d8d72", "shasum": "" }, "require": { @@ -2519,11 +3696,15 @@ "type": "library", "autoload": { "psr-4": { - "WhichBrowser\\": ["src/", "tests/src/"] + "WhichBrowser\\": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Niels Leenheer", @@ -2533,40 +3714,269 @@ ], "description": "Useragent sniffing library for PHP", "homepage": "http://whichbrowser.net", - "keywords": ["browser", "sniffing", "ua", "useragent"], + "keywords": [ + "browser", + "sniffing", + "ua", + "useragent" + ], "support": { "issues": "https://github.com/WhichBrowser/Parser-PHP/issues", - "source": "https://github.com/WhichBrowser/Parser-PHP/tree/v2.1.7" + "source": "https://github.com/WhichBrowser/Parser-PHP/tree/v2.1.8" }, - "time": "2022-04-19T20:14:54+00:00" + "time": "2024-04-17T12:47:41+00:00" + }, + { + "name": "yassinedoghri/codeigniter-vite", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/yassinedoghri/codeigniter-vite.git", + "reference": "95c1dd30b716e3204ce981aa564202b299f9c8a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yassinedoghri/codeigniter-vite/zipball/95c1dd30b716e3204ce981aa564202b299f9c8a5", + "reference": "95c1dd30b716e3204ce981aa564202b299f9c8a5", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "codeigniter/phpstan-codeigniter": "^1.5.4", + "codeigniter4/framework": "^v4.6.0", + "pestphp/pest": "^3.8.4", + "pestphp/pest-plugin-type-coverage": "^3.6.1", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "rector/rector": "^2.1.4", + "symplify/coding-standard": "^12.4.3", + "symplify/easy-coding-standard": "^12.5.24" + }, + "type": "library", + "autoload": { + "psr-4": { + "CodeIgniterVite\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Yassine Doghri", + "homepage": "https://yassinedoghri.com/" + } + ], + "description": "An opinionated Vite integration for CodeIgniter4 projects.", + "keywords": [ + "codeigniter", + "codeigniter4", + "iconify", + "icons", + "php-icons" + ], + "support": { + "issues": "https://github.com/yassinedoghri/codeigniter-vite/issues", + "source": "https://github.com/yassinedoghri/codeigniter-vite/tree/v2.1.0" + }, + "time": "2025-09-09T18:36:45+00:00" + }, + { + "name": "yassinedoghri/php-icons", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/yassinedoghri/php-icons.git", + "reference": "ae5d7727431f6891a0660d2b20818795fae40b41" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yassinedoghri/php-icons/zipball/ae5d7727431f6891a0660d2b20818795fae40b41", + "reference": "ae5d7727431f6891a0660d2b20818795fae40b41", + "shasum": "" + }, + "require": { + "adhocore/cli": "^v1.9.3", + "composer-runtime-api": "^2.2", + "php": ">=8.1" + }, + "require-dev": { + "kint-php/kint": "^6.0.1", + "pestphp/pest": "^3.7.4", + "pestphp/pest-plugin-type-coverage": "^3.4.0", + "phpstan/phpstan": "^2.1.10", + "rector/rector": "^2.0.10", + "symplify/coding-standard": "^12.2.3", + "symplify/easy-coding-standard": "^12.5.9" + }, + "bin": [ + "bin/php-icons" + ], + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Console/helpers.php" + ], + "psr-4": { + "PHPIcons\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Yassine Doghri", + "email": "yassine@doghri.fr", + "homepage": "https://yassinedoghri.com", + "role": "Maintainer" + } + ], + "description": "A PHP library based on iconify's API to download and render svg icons from popular open source icon sets.", + "support": { + "issues": "https://github.com/yassinedoghri/php-icons/issues", + "source": "https://github.com/yassinedoghri/php-icons/tree/v1.3.0" + }, + "time": "2025-03-23T16:46:25+00:00" + }, + { + "name": "yassinedoghri/podcast-feed", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/yassinedoghri/podcast-feed.git", + "reference": "d617e204fe85e0b7bd12b9d382cae4064af280c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yassinedoghri/podcast-feed/zipball/d617e204fe85e0b7bd12b9d382cae4064af280c8", + "reference": "d617e204fe85e0b7bd12b9d382cae4064af280c8", + "shasum": "" + }, + "require": { + "ext-intl": "*", + "php": ">=8.1" + }, + "require-dev": { + "kint-php/kint": "^5.0.5", + "phpstan/phpstan": "^1.10.18", + "rector/rector": "^0.17.0", + "symplify/coding-standard": "^11.3.0", + "symplify/easy-coding-standard": "^11.3.4" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "PodcastFeed\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "AGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Yassine Doghri", + "email": "yassine@doghri.fr", + "homepage": "https://yassinedoghri.com", + "role": "Maintainer" + } + ], + "description": "A robust podcast feed parser and validator written in PHP.", + "support": { + "issues": "https://github.com/yassinedoghri/podcast-feed/issues", + "source": "https://github.com/yassinedoghri/podcast-feed/tree/main" + }, + "time": "2024-04-28T16:17:41+00:00" + }, + { + "name": "z4kn4fein/php-semver", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/z4kn4fein/php-semver.git", + "reference": "049a1d81e92235c8b3c9ab30a96fcbaa929a266d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/z4kn4fein/php-semver/zipball/049a1d81e92235c8b3c9ab30a96fcbaa929a266d", + "reference": "049a1d81e92235c8b3c9ab30a96fcbaa929a266d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.0", + "phpstan/phpstan": "^1.0", + "phpunit/phpunit": "^10" + }, + "type": "library", + "autoload": { + "psr-4": { + "z4kn4fein\\SemVer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Peter Csajtai", + "email": "peter.csajtai@outlook.com" + } + ], + "description": "Semantic Versioning library for PHP. It implements the full semantic version 2.0.0 specification and provides ability to parse, compare, and increment semantic versions along with validation against constraints.", + "homepage": "https://github.com/z4kn4fein/php-semver", + "keywords": [ + "comparison", + "semantic", + "semver", + "validation", + "version", + "versioning" + ], + "support": { + "issues": "https://github.com/z4kn4fein/php-semver/issues", + "source": "https://github.com/z4kn4fein/php-semver/tree/v3.0.0" + }, + "time": "2024-04-01T16:17:27+00:00" } ], "packages-dev": [ { "name": "captainhook/captainhook", - "version": "5.10.11", + "version": "5.28.3", "source": { "type": "git", - "url": "https://github.com/captainhookphp/captainhook.git", - "reference": "377ea566c5fb91e2fbec6ad0aadf21eb88e18cee" + "url": "https://github.com/captainhook-git/captainhook.git", + "reference": "5d35b249f3843ef36ead119f4347e649278ad6d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/captainhookphp/captainhook/zipball/377ea566c5fb91e2fbec6ad0aadf21eb88e18cee", - "reference": "377ea566c5fb91e2fbec6ad0aadf21eb88e18cee", + "url": "https://api.github.com/repos/captainhook-git/captainhook/zipball/5d35b249f3843ef36ead119f4347e649278ad6d8", + "reference": "5d35b249f3843ef36ead119f4347e649278ad6d8", "shasum": "" }, "require": { + "captainhook/secrets": "^0.9.4", "ext-json": "*", "ext-spl": "*", "ext-xml": "*", - "php": ">=7.2", + "php": ">=8.0", "sebastianfeldmann/camino": "^0.9.2", "sebastianfeldmann/cli": "^3.3", - "sebastianfeldmann/git": "^3.8.1", - "symfony/console": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0", - "symfony/filesystem": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0", - "symfony/process": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0" + "sebastianfeldmann/git": "^3.16.0", + "symfony/console": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "symfony/filesystem": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "symfony/process": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0" }, "replace": { "sebastianfeldmann/captainhook": "*" @@ -2575,14 +3985,16 @@ "composer/composer": "~1 || ^2.0", "mikey179/vfsstream": "~1" }, - "bin": ["bin/captainhook"], + "bin": [ + "bin/captainhook" + ], "type": "library", "extra": { - "branch-alias": { - "dev-main": "6.0.x-dev" - }, "captainhook": { "config": "captainhook.json" + }, + "branch-alias": { + "dev-main": "6.0.x-dev" } }, "autoload": { @@ -2591,7 +4003,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Sebastian Feldmann", @@ -2599,7 +4013,7 @@ } ], "description": "PHP git hook manager", - "homepage": "https://github.com/captainhookphp/captainhook", + "homepage": "https://php.captainhook.info/", "keywords": [ "commit-msg", "git", @@ -2610,8 +4024,8 @@ "prepare-commit-msg" ], "support": { - "issues": "https://github.com/captainhookphp/captainhook/issues", - "source": "https://github.com/captainhookphp/captainhook/tree/5.10.11" + "issues": "https://github.com/captainhook-git/captainhook/issues", + "source": "https://github.com/captainhook-git/captainhook/tree/5.28.3" }, "funding": [ { @@ -2619,32 +4033,232 @@ "type": "github" } ], - "time": "2022-07-26T20:11:41+00:00" + "time": "2026-02-16T14:08:58+00:00" }, { - "name": "composer/pcre", - "version": "3.0.0", + "name": "captainhook/secrets", + "version": "0.9.7", "source": { "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd" + "url": "https://github.com/captainhook-git/secrets.git", + "reference": "d62c97f75f81ac98e22f1c282482bd35fa82f631" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/e300eb6c535192decd27a85bc72a9290f0d6b3bd", - "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd", + "url": "https://api.github.com/repos/captainhook-git/secrets/zipball/d62c97f75f81ac98e22f1c282482bd35fa82f631", + "reference": "d62c97f75f81ac98e22f1c282482bd35fa82f631", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "CaptainHook\\Secrets\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sebastian Feldmann", + "email": "sf@sebastian-feldmann.info" + } + ], + "description": "Utility classes to detect secrets", + "keywords": [ + "commit-msg", + "keys", + "passwords", + "post-merge", + "prepare-commit-msg", + "secrets", + "tokens" + ], + "support": { + "issues": "https://github.com/captainhook-git/secrets/issues", + "source": "https://github.com/captainhook-git/secrets/tree/0.9.7" + }, + "funding": [ + { + "url": "https://github.com/sponsors/sebastianfeldmann", + "type": "github" + } + ], + "time": "2025-04-08T07:10:48+00:00" + }, + { + "name": "clue/ndjson-react", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\NDJson\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", + "keywords": [ + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" + ], + "support": { + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-12-23T10:58:28+00:00" + }, + { + "name": "codeigniter/phpstan-codeigniter", + "version": "v1.5.4", + "source": { + "type": "git", + "url": "https://github.com/CodeIgniter/phpstan-codeigniter.git", + "reference": "e959fb0841c29a01870aa6570f3095f42e1057ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CodeIgniter/phpstan-codeigniter/zipball/e959fb0841c29a01870aa6570f3095f42e1057ad", + "reference": "e959fb0841c29a01870aa6570f3095f42e1057ad", + "shasum": "" + }, + "require": { + "php": "^8.1", + "phpstan/phpstan": "^2.0" + }, + "conflict": { + "codeigniter/framework": "*" + }, + "require-dev": { + "codeigniter/coding-standard": "^1.7", + "codeigniter4/framework": "^4.5", + "codeigniter4/shield": "^1.0", + "friendsofphp/php-cs-fixer": "^3.49", + "nexusphp/cs-config": "^3.21", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^10.5 || ^11.4" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "CodeIgniter\\PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Paul E. Balandan, CPA", + "email": "paulbalandan@gmail.com" + } + ], + "description": "CodeIgniter extensions and rules for PHPStan", + "keywords": [ + "PHPStan", + "codeigniter", + "codeigniter4", + "dev", + "static analysis" + ], + "support": { + "forum": "http://forum.codeigniter.com/", + "issues": "https://github.com/CodeIgniter/phpstan-codeigniter/issues", + "slack": "https://codeigniterchat.slack.com", + "source": "https://github.com/CodeIgniter/phpstan-codeigniter" + }, + "time": "2025-06-24T17:28:48+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, "require-dev": { - "phpstan/phpstan": "^1.3", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" }, "type": "library", "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, "branch-alias": { "dev-main": "3.x-dev" } @@ -2655,7 +4269,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Jordi Boggiano", @@ -2664,10 +4280,15 @@ } ], "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": ["PCRE", "preg", "regex", "regular expression"], + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.0.0" + "source": "https://github.com/composer/pcre/tree/3.3.2" }, "funding": [ { @@ -2683,28 +4304,28 @@ "type": "tidelift" } ], - "time": "2022-02-25T20:21:48+00:00" + "time": "2024-11-12T16:29:46+00:00" }, { "name": "composer/semver", - "version": "3.3.2", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { @@ -2718,7 +4339,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nils Adermann", @@ -2737,11 +4360,16 @@ } ], "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": ["semantic", "semver", "validation", "versioning"], + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.3.2" + "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { @@ -2751,26 +4379,22 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2022-04-01T19:23:25+00:00" + "time": "2025-08-20T19:15:30+00:00" }, { "name": "composer/xdebug-handler", - "version": "3.0.3", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "ced299686f41dce890debac69273b47ffe98a40c" + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", - "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { @@ -2781,7 +4405,7 @@ "require-dev": { "phpstan/phpstan": "^1.0", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^6.0" + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "type": "library", "autoload": { @@ -2790,7 +4414,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "John Stevenson", @@ -2798,11 +4424,14 @@ } ], "description": "Restarts a process without Xdebug.", - "keywords": ["Xdebug", "performance"], + "keywords": [ + "Xdebug", + "performance" + ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" }, "funding": [ { @@ -2818,270 +4447,191 @@ "type": "tidelift" } ], - "time": "2022-02-25T21:32:43+00:00" + "time": "2024-05-06T16:37:16+00:00" }, { - "name": "doctrine/annotations", - "version": "1.13.3", + "name": "evenement/evenement", + "version": "v3.0.2", "source": { "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "648b0343343565c4a056bfc8392201385e8d89f0" + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/648b0343343565c4a056bfc8392201385e8d89f0", - "reference": "648b0343343565c4a056bfc8392201385e8d89f0", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", - "ext-tokenizer": "*", - "php": "^7.1 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" + "php": ">=7.0" }, "require-dev": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^6.0 || ^8.1", - "phpstan/phpstan": "^1.4.10 || ^1.8.0", - "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", - "symfony/cache": "^4.4 || ^5.2", - "vimeo/psalm": "^4.10" + "phpunit/phpunit": "^9 || ^6" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + "Evenement\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" } ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", - "keywords": ["annotations", "docblock", "parser"], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.13.3" + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" }, - "time": "2022-07-02T10:48:51+00:00" + "time": "2023-08-08T05:53:35+00:00" }, { - "name": "doctrine/instantiator", - "version": "1.4.1", + "name": "fidry/cpu-core-counter", + "version": "1.3.0", "source": { "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + "Fidry\\CpuCoreCounter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" } ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": ["constructor", "instantiate"], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" }, "funding": [ { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" + "url": "https://github.com/theofidry", + "type": "github" } ], - "time": "2022-03-03T08:28:38+00:00" - }, - { - "name": "doctrine/lexer", - "version": "1.2.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", - "keywords": ["annotations", "docblock", "lexer", "parser", "php"], - "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.3" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" - } - ], - "time": "2022-02-28T11:07:21+00:00" + "time": "2025-08-14T07:29:31+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.12.0", + "version": "v3.94.1", "source": { "type": "git", - "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "eae11d945e2885d86e1c080eec1bb30a2aa27998" + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "d1a3634e29916367b885250e1fc4dfd5ffe3b091" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/eae11d945e2885d86e1c080eec1bb30a2aa27998", - "reference": "eae11d945e2885d86e1c080eec1bb30a2aa27998", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/d1a3634e29916367b885250e1fc4dfd5ffe3b091", + "reference": "d1a3634e29916367b885250e1fc4dfd5ffe3b091", "shasum": "" }, "require": { - "composer/semver": "^3.2", - "composer/xdebug-handler": "^3.0.3", - "doctrine/annotations": "^1.13", + "clue/ndjson-react": "^1.3", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.5", + "ext-filter": "*", + "ext-hash": "*", "ext-json": "*", "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.3", "php": "^7.4 || ^8.0", - "sebastian/diff": "^4.0", - "symfony/console": "^5.4 || ^6.0", - "symfony/event-dispatcher": "^5.4 || ^6.0", - "symfony/filesystem": "^5.4 || ^6.0", - "symfony/finder": "^5.4 || ^6.0", - "symfony/options-resolver": "^5.4 || ^6.0", - "symfony/polyfill-mbstring": "^1.23", - "symfony/polyfill-php80": "^1.25", - "symfony/polyfill-php81": "^1.25", - "symfony/process": "^5.4 || ^6.0", - "symfony/stopwatch": "^5.4 || ^6.0" + "react/child-process": "^0.6.6", + "react/event-loop": "^1.5", + "react/socket": "^1.16", + "react/stream": "^1.4", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0", + "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.33", + "symfony/polyfill-php80": "^1.33", + "symfony/polyfill-php81": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2 || ^8.0", + "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" }, "require-dev": { - "justinrainbow/json-schema": "^5.2", - "keradus/cli-executor": "^1.5", - "mikey179/vfsstream": "^1.6.10", - "php-coveralls/php-coveralls": "^2.5.2", - "php-cs-fixer/accessible-object": "^1.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", - "phpspec/prophecy": "^1.15", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5", - "phpunitgoodpractices/polyfill": "^1.6", - "phpunitgoodpractices/traits": "^1.9.2", - "symfony/phpunit-bridge": "^6.0", - "symfony/yaml": "^5.4 || ^6.0" + "facile-it/paraunit": "^1.3.1 || ^2.7.1", + "infection/infection": "^0.32.3", + "justinrainbow/json-schema": "^6.6.4", + "keradus/cli-executor": "^2.3", + "mikey179/vfsstream": "^1.6.12", + "php-coveralls/php-coveralls": "^2.9.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.7", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.7", + "phpunit/phpunit": "^9.6.34 || ^10.5.63 || ^11.5.51", + "symfony/polyfill-php85": "^1.33", + "symfony/var-dumper": "^5.4.48 || ^6.4.32 || ^7.4.4 || ^8.0.4", + "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0.1" }, "suggest": { "ext-dom": "For handling output formats in XML", "ext-mbstring": "For handling non-UTF8 characters." }, - "bin": ["php-cs-fixer"], + "bin": [ + "php-cs-fixer" + ], "type": "application", "autoload": { "psr-4": { "PhpCsFixer\\": "src/" - } + }, + "exclude-from-classmap": [ + "src/**/Internal/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -3093,9 +4643,15 @@ } ], "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], "support": { - "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", - "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.12.0" + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.94.1" }, "funding": [ { @@ -3103,27 +4659,28 @@ "type": "github" } ], - "time": "2022-10-12T14:20:51+00:00" + "time": "2026-02-18T12:24:42+00:00" }, { "name": "mikey179/vfsstream", - "version": "v1.6.11", + "version": "v1.6.12", "source": { "type": "git", "url": "https://github.com/bovigo/vfsStream.git", - "reference": "17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f" + "reference": "fe695ec993e0a55c3abdda10a9364eb31c6f1bf0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f", - "reference": "17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f", + "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/fe695ec993e0a55c3abdda10a9364eb31c6f1bf0", + "reference": "fe695ec993e0a55c3abdda10a9364eb31c6f1bf0", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.1.0" }, "require-dev": { - "phpunit/phpunit": "^4.5|^5.0" + "phpunit/phpunit": "^7.5||^8.5||^9.6", + "yoast/phpunit-polyfills": "^2.0" }, "type": "library", "extra": { @@ -3137,7 +4694,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Frank Kleine", @@ -3152,20 +4711,20 @@ "source": "https://github.com/bovigo/vfsStream/tree/master", "wiki": "https://github.com/bovigo/vfsStream/wiki" }, - "time": "2022-02-23T02:02:42+00:00" + "time": "2024-08-29T18:43:31+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -3173,27 +4732,38 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { - "files": ["src/DeepCopy/deep_copy.php"], + "files": [ + "src/DeepCopy/deep_copy.php" + ], "psr-4": { "DeepCopy\\": "src/DeepCopy/" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "description": "Create deep copies (clones) of your objects", - "keywords": ["clone", "copy", "duplicate", "object", "object graph"], + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -3201,35 +4771,39 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nikic/php-parser", - "version": "v4.15.1", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", - "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, - "bin": ["bin/php-parse"], + "bin": [ + "bin/php-parse" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -3238,36 +4812,42 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Nikita Popov" } ], "description": "A PHP parser written in PHP", - "keywords": ["parser", "php"], + "keywords": [ + "parser", + "php" + ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2022-09-04T07:30:47+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -3280,10 +4860,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Arne Blankerts", @@ -3304,9 +4888,15 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -3327,10 +4917,14 @@ }, "type": "library", "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Arne Blankerts", @@ -3356,37 +4950,93 @@ "time": "2022-02-21T01:04:05+00:00" }, { - "name": "phpstan/phpstan", - "version": "1.8.9", + "name": "phpstan/extension-installer", + "version": "1.4.3", "source": { "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "3a72d9d9f2528fbd50c2d8fcf155fd9f74ade3f2" + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3a72d9d9f2528fbd50c2d8fcf155fd9f74ade3f2", - "reference": "3a72d9d9f2528fbd50c2d8fcf155fd9f74ade3f2", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.4.3" + }, + "time": "2024-09-04T20:21:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.39", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c6f73a2af4cbcd99c931d0fb8f08548cc0fa8224", + "reference": "c6f73a2af4cbcd99c931d0fb8f08548cc0fa8224", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" }, - "bin": ["phpstan", "phpstan.phar"], + "bin": [ + "phpstan", + "phpstan.phar" + ], "type": "library", "autoload": { - "files": ["bootstrap.php"] + "files": [ + "bootstrap.php" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "description": "PHPStan - PHP Static Analysis Tool", - "keywords": ["dev", "static analysis"], + "keywords": [ + "dev", + "static analysis" + ], "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.8.9" + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" }, "funding": [ { @@ -3396,61 +5046,60 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2022-10-13T13:40:18+00:00" + "time": "2026-02-11T14:48:56+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.17", + "version": "13.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8" + "reference": "a8b58fde2f4fbc69a064e1f80ff917607cf7737c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8", - "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/a8b58fde2f4fbc69a064e1f80ff917607cf7737c", + "reference": "a8b58fde2f4fbc69a064e1f80ff917607cf7737c", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.14", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "nikic/php-parser": "^5.7.0", + "php": ">=8.4", + "phpunit/php-file-iterator": "^7.0", + "phpunit/php-text-template": "^6.0", + "sebastian/complexity": "^6.0", + "sebastian/environment": "^9.0", + "sebastian/lines-of-code": "^5.0", + "sebastian/version": "^7.0", + "theseer/tokenizer": "^2.0.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^13.0" }, "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "13.0.x-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -3460,50 +5109,71 @@ ], "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": ["coverage", "testing", "xunit"], + "keywords": [ + "coverage", + "testing", + "xunit" + ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/13.0.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" } ], - "time": "2022-08-30T12:24:04+00:00" + "time": "2026-02-06T06:05:15+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.6", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", + "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -3513,39 +5183,55 @@ ], "description": "FilterIterator implementation that filters files based on a list of suffixes.", "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": ["filesystem", "iterator"], + "keywords": [ + "filesystem", + "iterator" + ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/7.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" } ], - "time": "2021-12-02T12:48:52+00:00" + "time": "2026-02-06T04:33:26+00:00" }, { "name": "phpunit/php-invoker", - "version": "3.1.1", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", + "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.4" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^13.0" }, "suggest": { "ext-pcntl": "*" @@ -3553,14 +5239,18 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-main": "7.0-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -3570,50 +5260,69 @@ ], "description": "Invoke callables with a timeout", "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": ["process"], + "keywords": [ + "process" + ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/7.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-invoker", + "type": "tidelift" } ], - "time": "2020-09-28T05:58:55+00:00" + "time": "2026-02-06T04:34:47+00:00" }, { "name": "phpunit/php-text-template", - "version": "2.0.4", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/a47af19f93f76aa3368303d752aa5272ca3299f4", + "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -3623,50 +5332,69 @@ ], "description": "Simple template engine.", "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": ["template"], + "keywords": [ + "template" + ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/6.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-text-template", + "type": "tidelift" } ], - "time": "2020-10-26T05:33:50+00:00" + "time": "2026-02-06T04:36:37+00:00" }, { "name": "phpunit/php-timer", - "version": "5.0.3", + "version": "9.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a0e12065831f6ab0d83120dc61513eb8d9a966f6", + "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "9.0-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -3676,79 +5404,97 @@ ], "description": "Utility class for timing", "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": ["timer"], + "keywords": [ + "timer" + ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/9.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-timer", + "type": "tidelift" } ], - "time": "2020-10-26T13:16:10+00:00" + "time": "2026-02-06T04:37:53+00:00" }, { "name": "phpunit/phpunit", - "version": "9.5.25", + "version": "13.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d" + "reference": "d57826e8921a534680c613924bfd921ded8047f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", - "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d57826e8921a534680c613924bfd921ded8047f4", + "reference": "d57826e8921a534680c613924bfd921ded8047f4", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", - "sebastian/version": "^3.0.2" + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.4.1", + "phpunit/php-code-coverage": "^13.0.1", + "phpunit/php-file-iterator": "^7.0.0", + "phpunit/php-invoker": "^7.0.0", + "phpunit/php-text-template": "^6.0.0", + "phpunit/php-timer": "^9.0.0", + "sebastian/cli-parser": "^5.0.0", + "sebastian/comparator": "^8.0.0", + "sebastian/diff": "^8.0.0", + "sebastian/environment": "^9.0.0", + "sebastian/exporter": "^8.0.0", + "sebastian/global-state": "^9.0.0", + "sebastian/object-enumerator": "^8.0.0", + "sebastian/recursion-context": "^8.0.0", + "sebastian/type": "^7.0.0", + "sebastian/version": "^7.0.0", + "staabm/side-effects-detector": "^1.0.5" }, - "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" - }, - "bin": ["phpunit"], + "bin": [ + "phpunit" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "9.5-dev" + "dev-main": "13.0-dev" } }, "autoload": { - "files": ["src/Framework/Assert/Functions.php"], - "classmap": ["src/"] + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -3758,10 +5504,15 @@ ], "description": "The PHP Unit Testing framework.", "homepage": "https://phpunit.de/", - "keywords": ["phpunit", "testing", "xunit"], + "keywords": [ + "phpunit", + "testing", + "xunit" + ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.25" + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/13.0.5" }, "funding": [ { @@ -3772,12 +5523,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2022-09-25T03:44:45+00:00" + "time": "2026-02-18T12:40:03+00:00" }, { "name": "psr/container", @@ -3808,7 +5567,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "PHP-FIG", @@ -3831,48 +5592,582 @@ "time": "2021-11-05T16:47:00+00:00" }, { - "name": "rector/rector", - "version": "0.14.5", + "name": "react/cache", + "version": "v1.2.0", "source": { "type": "git", - "url": "https://github.com/rectorphp/rector.git", - "reference": "f7fd87b2435835f481e6a94ee28e09af412bd3cc" + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/f7fd87b2435835f481e6a94ee28e09af412bd3cc", - "reference": "f7fd87b2435835f481e6a94ee28e09af412bd3cc", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", "shasum": "" }, "require": { - "php": "^7.2|^8.0", - "phpstan/phpstan": "^1.8.6" + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/child-process", + "version": "v0.6.7", + "source": { + "type": "git", + "url": "https://github.com/reactphp/child-process.git", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/970f0e71945556422ee4570ccbabaedc3cf04ad3", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/socket": "^1.16", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\ChildProcess\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven library for executing child processes with ReactPHP.", + "keywords": [ + "event-driven", + "process", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.7" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-12-23T15:25:20+00:00" + }, + { + "name": "react/dns", + "version": "v1.14.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.14.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-18T19:34:28+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.6.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-17T20:46:25+00:00" + }, + { + "name": "react/promise", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.12.28 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-08-19T18:57:03+00:00" + }, + { + "name": "react/socket", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.17.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-19T20:47:34+00:00" + }, + { + "name": "react/stream", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, + { + "name": "rector/rector", + "version": "2.3.6", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "ca9ebb81d280cd362ea39474dabd42679e32ca6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/ca9ebb81d280cd362ea39474dabd42679e32ca6b", + "reference": "ca9ebb81d280cd362ea39474dabd42679e32ca6b", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.1.38" }, "conflict": { - "rector/rector-cakephp": "*", "rector/rector-doctrine": "*", - "rector/rector-laravel": "*", - "rector/rector-php-parser": "*", - "rector/rector-phpoffice": "*", + "rector/rector-downgrade-php": "*", "rector/rector-phpunit": "*", "rector/rector-symfony": "*" }, - "bin": ["bin/rector"], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "0.14-dev" - } + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" }, + "bin": [ + "bin/rector" + ], + "type": "library", "autoload": { - "files": ["bootstrap.php"] + "files": [ + "bootstrap.php" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "homepage": "https://getrector.com/", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/0.14.5" + "source": "https://github.com/rectorphp/rector/tree/2.3.6" }, "funding": [ { @@ -3880,39 +6175,43 @@ "type": "github" } ], - "time": "2022-09-29T11:05:42+00:00" + "time": "2026-02-06T14:25:06+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/48a4654fa5e48c1c81214e9930048a572d4b23ca", + "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -3924,152 +6223,71 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/5.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - } - ], - "time": "2020-09-28T06:08:49+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "1.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": ["src/"] - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], - "authors": [ + }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" - }, - "funding": [ + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:08:54+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": ["src/"] - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], - "authors": [ + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser", + "type": "tidelift" } ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:30:19+00:00" + "time": "2026-02-06T04:39:44+00:00" }, { "name": "sebastian/comparator", - "version": "4.0.8", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + "reference": "29b232ddc29c2b114c0358c69b3084e7c3da0d58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/29b232ddc29c2b114c0358c69b3084e7c3da0d58", + "reference": "29b232ddc29c2b114c0358c69b3084e7c3da0d58", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.4", + "sebastian/diff": "^8.0", + "sebastian/exporter": "^8.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^13.0" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -4090,51 +6308,72 @@ ], "description": "Provides the functionality to compare PHP values for equality", "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": ["comparator", "compare", "equality"], + "keywords": [ + "comparator", + "compare", + "equality" + ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/8.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2022-09-14T12:41:17+00:00" + "time": "2026-02-06T04:40:39+00:00" }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "c5651c795c98093480df79350cb050813fc7a2f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/c5651c795c98093480df79350cb050813fc7a2f3", + "reference": "c5651c795c98093480df79350cb050813fc7a2f3", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", - "php": ">=7.3" + "nikic/php-parser": "^5.0", + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -4146,48 +6385,65 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/6.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/complexity", + "type": "tidelift" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2026-02-06T04:41:32+00:00" }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "a2b6d09d7729ee87d605a439469f9dcc39be5ea3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/a2b6d09d7729ee87d605a439469f9dcc39be5ea3", + "reference": "a2b6d09d7729ee87d605a439469f9dcc39be5ea3", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" + "phpunit/phpunit": "^13.0", + "symfony/process": "^7.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -4200,38 +6456,56 @@ ], "description": "Diff implementation", "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": ["diff", "udiff", "unidiff", "unified diff"], + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/8.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/diff", + "type": "tidelift" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2026-02-06T04:42:27+00:00" }, { "name": "sebastian/environment", - "version": "5.1.4", + "version": "9.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + "reference": "bb64d08145b021b67d5f253308a498b73ab0461e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/bb64d08145b021b67d5f253308a498b73ab0461e", + "reference": "bb64d08145b021b67d5f253308a498b73ab0461e", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^13.0" }, "suggest": { "ext-posix": "*" @@ -4239,14 +6513,18 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-main": "9.0-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -4254,53 +6532,74 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": ["Xdebug", "environment", "hhvm"], + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/9.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" } ], - "time": "2022-04-03T09:37:03+00:00" + "time": "2026-02-06T04:43:29+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.5", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + "reference": "dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea", + "reference": "dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "ext-mbstring": "*", + "php": ">=8.4", + "sebastian/recursion-context": "^8.0" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -4325,56 +6624,73 @@ ], "description": "Provides the functionality to export PHP variables for visualization", "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": ["export", "exporter"], + "keywords": [ + "export", + "exporter" + ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/8.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2022-09-14T06:03:37+00:00" + "time": "2026-02-06T04:44:28+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.5", + "version": "9.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e52e3dc22441e6218c710afe72c3042f8fc41ea7", + "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.4", + "sebastian/object-reflector": "^6.0", + "sebastian/recursion-context": "^8.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "9.0-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -4382,52 +6698,71 @@ } ], "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": ["global state"], + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/9.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2026-02-06T04:45:13+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "4f21bb7768e1c997722ccc7efb1d6b5c11bfd471" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/4f21bb7768e1c997722ccc7efb1d6b5c11bfd471", + "reference": "4f21bb7768e1c997722ccc7efb1d6b5c11bfd471", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", - "php": ">=7.3" + "nikic/php-parser": "^5.0", + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -4439,49 +6774,66 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/5.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/lines-of-code", + "type": "tidelift" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2026-02-06T04:45:54+00:00" }, { "name": "sebastian/object-enumerator", - "version": "4.0.4", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", + "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.4", + "sebastian/object-reflector": "^6.0", + "sebastian/recursion-context": "^8.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -4492,47 +6844,64 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/8.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/object-enumerator", + "type": "tidelift" } ], - "time": "2020-10-26T13:12:34+00:00" + "time": "2026-02-06T04:46:36+00:00" }, { "name": "sebastian/object-reflector", - "version": "2.0.4", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", + "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -4543,47 +6912,64 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/6.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/object-reflector", + "type": "tidelift" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2026-02-06T04:47:13+00:00" }, { "name": "sebastian/recursion-context", - "version": "4.0.4", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/74c5af21f6a5833e91767ca068c4d3dfec15317e", + "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -4599,101 +6985,67 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/8.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - } - ], - "time": "2020-10-26T13:17:30+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": ["src/"] - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], - "authors": [ + }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" - }, - "funding": [ + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2026-02-06T04:51:28+00:00" }, { "name": "sebastian/type", - "version": "3.2.0", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" + "reference": "42412224607bd3931241bbd17f38e0f972f5a916" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/42412224607bd3931241bbd17f38e0f972f5a916", + "reference": "42412224607bd3931241bbd17f38e0f972f5a916", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-main": "7.0-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -4705,44 +7057,61 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/7.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" } ], - "time": "2022-09-12T14:47:03+00:00" + "time": "2026-02-06T04:52:09+00:00" }, { "name": "sebastian/version", - "version": "3.0.2", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/ad37a5552c8e2b88572249fdc19b6da7792e021b", + "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -4754,15 +7123,28 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/7.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/version", + "type": "tidelift" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2026-02-06T04:52:52+00:00" }, { "name": "sebastianfeldmann/camino", @@ -4793,7 +7175,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Sebastian Feldmann", @@ -4802,7 +7186,10 @@ ], "description": "Path management the OO way", "homepage": "https://github.com/sebastianfeldmann/camino", - "keywords": ["file system", "path"], + "keywords": [ + "file system", + "path" + ], "support": { "issues": "https://github.com/sebastianfeldmann/camino/issues", "source": "https://github.com/sebastianfeldmann/camino/tree/0.9.5" @@ -4817,16 +7204,16 @@ }, { "name": "sebastianfeldmann/cli", - "version": "3.4.1", + "version": "3.4.2", "source": { "type": "git", "url": "https://github.com/sebastianfeldmann/cli.git", - "reference": "8a932e99e9455981fb32fa6c085492462fe8f8cf" + "reference": "6fa122afd528dae7d7ec988a604aa6c600f5d9b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianfeldmann/cli/zipball/8a932e99e9455981fb32fa6c085492462fe8f8cf", - "reference": "8a932e99e9455981fb32fa6c085492462fe8f8cf", + "url": "https://api.github.com/repos/sebastianfeldmann/cli/zipball/6fa122afd528dae7d7ec988a604aa6c600f5d9b5", + "reference": "6fa122afd528dae7d7ec988a604aa6c600f5d9b5", "shasum": "" }, "require": { @@ -4847,7 +7234,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Sebastian Feldmann", @@ -4856,10 +7245,12 @@ ], "description": "PHP cli helper classes", "homepage": "https://github.com/sebastianfeldmann/cli", - "keywords": ["cli"], + "keywords": [ + "cli" + ], "support": { "issues": "https://github.com/sebastianfeldmann/cli/issues", - "source": "https://github.com/sebastianfeldmann/cli/tree/3.4.1" + "source": "https://github.com/sebastianfeldmann/cli/tree/3.4.2" }, "funding": [ { @@ -4867,26 +7258,27 @@ "type": "github" } ], - "time": "2021-12-20T14:59:49+00:00" + "time": "2024-11-26T10:19:01+00:00" }, { "name": "sebastianfeldmann/git", - "version": "3.8.4", + "version": "3.16.0", "source": { "type": "git", "url": "https://github.com/sebastianfeldmann/git.git", - "reference": "b324b6ba21871709812e34ad278a82c2e1c2965b" + "reference": "40a5cc043f0957228767f639e370ec92590e940f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianfeldmann/git/zipball/b324b6ba21871709812e34ad278a82c2e1c2965b", - "reference": "b324b6ba21871709812e34ad278a82c2e1c2965b", + "url": "https://api.github.com/repos/sebastianfeldmann/git/zipball/40a5cc043f0957228767f639e370ec92590e940f", + "reference": "40a5cc043f0957228767f639e370ec92590e940f", "shasum": "" }, "require": { "ext-json": "*", - "ext-xml": "*", - "php": ">=7.2", + "ext-libxml": "*", + "ext-simplexml": "*", + "php": ">=8.0", "sebastianfeldmann/cli": "^3.0" }, "require-dev": { @@ -4904,7 +7296,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Sebastian Feldmann", @@ -4913,10 +7307,12 @@ ], "description": "PHP git wrapper", "homepage": "https://github.com/sebastianfeldmann/git", - "keywords": ["git"], + "keywords": [ + "git" + ], "support": { "issues": "https://github.com/sebastianfeldmann/git/issues", - "source": "https://github.com/sebastianfeldmann/git/tree/3.8.4" + "source": "https://github.com/sebastianfeldmann/git/tree/3.16.0" }, "funding": [ { @@ -4924,136 +7320,109 @@ "type": "github" } ], - "time": "2022-08-26T07:03:13+00:00" + "time": "2026-01-26T20:59:18+00:00" }, { - "name": "symfony/config", - "version": "v6.1.3", + "name": "staabm/side-effects-detector", + "version": "1.0.5", "source": { "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "a0645dc585d378b73c01115dd7ab9348f7d40c85" + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/a0645dc585d378b73c01115dd7ab9348f7d40c85", - "reference": "a0645dc585d378b73c01115dd7ab9348f7d40c85", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/filesystem": "^5.4|^6.0", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/finder": "<5.4" + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" }, "require-dev": { - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/finder": "^5.4|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/yaml": "^5.4|^6.0" - }, - "suggest": { - "symfony/yaml": "To use the yaml reference dumper" + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" }, "type": "library", "autoload": { - "psr-4": { - "Symfony\\Component\\Config\\": "" - }, - "exclude-from-classmap": ["/Tests/"] + "classmap": [ + "lib/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" ], - "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", - "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v6.1.3" + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/staabm", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2022-07-20T15:00:40+00:00" + "time": "2024-10-20T05:08:20+00:00" }, { "name": "symfony/console", - "version": "v6.1.6", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "7fa3b9cf17363468795e539231a5c91b02b608fc" + "reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/7fa3b9cf17363468795e539231a5c91b02b608fc", - "reference": "7fa3b9cf17363468795e539231a5c91b02b608fc", + "url": "https://api.github.com/repos/symfony/console/zipball/ace03c4cf9805080ff40cbeec69fca180c339a3b", + "reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.4|^6.0" - }, - "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.4|^8.0" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/lock": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Console\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -5066,9 +7435,14 @@ ], "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", - "keywords": ["cli", "command line", "console", "terminal"], + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], "support": { - "source": "https://github.com/symfony/console/tree/v6.1.6" + "source": "https://github.com/symfony/console/tree/v8.0.4" }, "funding": [ { @@ -5080,86 +7454,7 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-10-07T08:04:03+00:00" - }, - { - "name": "symfony/dependency-injection", - "version": "v6.1.5", - "source": { - "type": "git", - "url": "https://github.com/symfony/dependency-injection.git", - "reference": "b9c797c9d56afc290d4265854bafd01b4e379240" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/b9c797c9d56afc290d4265854bafd01b4e379240", - "reference": "b9c797c9d56afc290d4265854bafd01b4e379240", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/service-contracts": "^1.1.6|^2.0|^3.0" - }, - "conflict": { - "ext-psr": "<1.1|>=2", - "symfony/config": "<6.1", - "symfony/finder": "<5.4", - "symfony/proxy-manager-bridge": "<5.4", - "symfony/yaml": "<5.4" - }, - "provide": { - "psr/container-implementation": "1.1|2.0", - "symfony/service-implementation": "1.1|2.0|3.0" - }, - "require-dev": { - "symfony/config": "^6.1", - "symfony/expression-language": "^5.4|^6.0", - "symfony/yaml": "^5.4|^6.0" - }, - "suggest": { - "symfony/config": "", - "symfony/expression-language": "For using expressions in service container configuration", - "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", - "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", - "symfony/yaml": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\DependencyInjection\\": "" - }, - "exclude-from-classmap": ["/Tests/"] - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows you to standardize and centralize the way objects are constructed in your application", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v6.1.5" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/nicolas-grekas", "type": "github" }, { @@ -5167,28 +7462,29 @@ "type": "tidelift" } ], - "time": "2022-09-28T16:00:52+00:00" + "time": "2026-01-13T13:06:50+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.1.0", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "a0449a7ad7daa0f7c0acd508259f80544ab5a347" + "reference": "99301401da182b6cfaa4700dbe9987bb75474b47" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a0449a7ad7daa0f7c0acd508259f80544ab5a347", - "reference": "a0449a7ad7daa0f7c0acd508259f80544ab5a347", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99301401da182b6cfaa4700dbe9987bb75474b47", + "reference": "99301401da182b6cfaa4700dbe9987bb75474b47", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/event-dispatcher-contracts": "^2|^3" + "php": ">=8.4", + "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<5.4" + "symfony/security-http": "<7.4", + "symfony/service-contracts": "<2.5" }, "provide": { "psr/event-dispatcher-implementation": "1.0", @@ -5196,27 +7492,28 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/error-handler": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/stopwatch": "^5.4|^6.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^7.4|^8.0" }, "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -5230,7 +7527,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.1.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v8.0.4" }, "funding": [ { @@ -5241,42 +7538,43 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-05-05T16:51:07+00:00" + "time": "2026-01-05T11:45:55+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.1.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "02ff5eea2f453731cfbc6bc215e456b781480448" + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/02ff5eea2f453731cfbc6bc215e456b781480448", - "reference": "02ff5eea2f453731cfbc6bc215e456b781480448", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", "shasum": "" }, "require": { "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.1-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { @@ -5285,7 +7583,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -5307,7 +7607,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.1.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" }, "funding": [ { @@ -5323,96 +7623,41 @@ "type": "tidelift" } ], - "time": "2022-02-25T11:15:52+00:00" - }, - { - "name": "symfony/filesystem", - "version": "v6.1.5", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "4d216a2beef096edf040a070117c39ca2abce307" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/4d216a2beef096edf040a070117c39ca2abce307", - "reference": "4d216a2beef096edf040a070117c39ca2abce307", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": ["/Tests/"] - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.1.5" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-09-21T20:29:40+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/finder", - "version": "v6.1.3", + "version": "v8.0.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709" + "reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/39696bff2c2970b3779a5cac7bf9f0b88fc2b709", - "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709", + "url": "https://api.github.com/repos/symfony/finder/zipball/8bd576e97c67d45941365bf824e18dc8538e6eb0", + "reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.4" }, "require-dev": { - "symfony/filesystem": "^6.0" + "symfony/filesystem": "^7.4|^8.0" }, "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -5426,7 +7671,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.1.3" + "source": "https://github.com/symfony/finder/tree/v8.0.5" }, "funding": [ { @@ -5437,40 +7682,48 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-07-29T07:42:06+00:00" + "time": "2026-01-26T15:08:38+00:00" }, { "name": "symfony/options-resolver", - "version": "v6.1.0", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "a3016f5442e28386ded73c43a32a5b68586dd1c4" + "reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/a3016f5442e28386ded73c43a32a5b68586dd1c4", - "reference": "a3016f5442e28386ded73c43a32a5b68586dd1c4", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/d2b592535ffa6600c265a3893a7f7fd2bad82dd7", + "reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3" + "php": ">=8.4", + "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\OptionsResolver\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -5483,9 +7736,13 @@ ], "description": "Provides an improved replacement for the array_replace PHP function", "homepage": "https://symfony.com", - "keywords": ["config", "configuration", "options"], + "keywords": [ + "config", + "configuration", + "options" + ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v6.1.0" + "source": "https://github.com/symfony/options-resolver/tree/v8.0.0" }, "funding": [ { @@ -5496,51 +7753,56 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-02-25T11:15:52+00:00" + "time": "2025-11-12T15:55:31+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.26.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "433d05519ce6990bf3530fba6957499d327395c2" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", - "reference": "433d05519ce6990bf3530fba6957499d327395c2", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "files": ["bootstrap.php"], + "files": [ + "bootstrap.php" + ], "psr-4": { "Symfony\\Polyfill\\Intl\\Grapheme\\": "" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -5562,7 +7824,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -5573,52 +7835,59 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.26.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "files": ["bootstrap.php"], + "files": [ + "bootstrap.php" + ], "psr-4": { "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, - "classmap": ["Resources/stubs"] + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -5640,7 +7909,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -5651,39 +7920,207 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/process", - "version": "v6.1.3", + "name": "symfony/polyfill-php81", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "a6506e99cfad7059b1ab5cab395854a0a0c21292" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/a6506e99cfad7059b1ab5cab395854a0a0c21292", - "reference": "a6506e99cfad7059b1ab5cab395854a0a0c21292", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+00:00" + }, + { + "name": "symfony/process", + "version": "v8.0.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674", + "reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674", + "shasum": "" + }, + "require": { + "php": ">=8.4" }, "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Process\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -5697,7 +8134,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.1.3" + "source": "https://github.com/symfony/process/tree/v8.0.5" }, "funding": [ { @@ -5708,55 +8145,61 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-06-27T17:24:16+00:00" + "time": "2026-01-26T15:08:38+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.1.1", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/925e713fe8fcacf6bc05e936edd8dd5441a21239", - "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { "php": ">=8.1", - "psr/container": "^2.0" + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "ext-psr": "<1.1|>=2" }, - "suggest": { - "symfony/service-implementation": "" - }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.1-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" }, - "exclude-from-classmap": ["/Test/"] + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -5778,7 +8221,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.1.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -5789,40 +8232,48 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-05-30T19:18:58+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/stopwatch", - "version": "v6.1.5", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "266636bb8f3fbdccc302491df7b3a1b9a8c238a7" + "reference": "67df1914c6ccd2d7b52f70d40cf2aea02159d942" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/266636bb8f3fbdccc302491df7b3a1b9a8c238a7", - "reference": "266636bb8f3fbdccc302491df7b3a1b9a8c238a7", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/67df1914c6ccd2d7b52f70d40cf2aea02159d942", + "reference": "67df1914c6ccd2d7b52f70d40cf2aea02159d942", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/service-contracts": "^1|^2|^3" + "php": ">=8.4", + "symfony/service-contracts": "^2.5|^3" }, "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Stopwatch\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -5836,7 +8287,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v6.1.5" + "source": "https://github.com/symfony/stopwatch/tree/v8.0.0" }, "funding": [ { @@ -5847,53 +8298,64 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-09-28T16:00:52+00:00" + "time": "2025-08-04T07:36:47+00:00" }, { "name": "symfony/string", - "version": "v6.1.6", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "7e7e0ff180d4c5a6636eaad57b65092014b61864" + "reference": "758b372d6882506821ed666032e43020c4f57194" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/7e7e0ff180d4c5a6636eaad57b65092014b61864", - "reference": "7e7e0ff180d4c5a6636eaad57b65092014b61864", + "url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194", + "reference": "758b372d6882506821ed666032e43020c4f57194", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { - "symfony/translation-contracts": "<2.0" + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/translation-contracts": "^2.0|^3.0", - "symfony/var-exporter": "^5.4|^6.0" + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^7.4|^8.0" }, "type": "library", "autoload": { - "files": ["Resources/functions.php"], + "files": [ + "Resources/functions.php" + ], "psr-4": { "Symfony\\Component\\String\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -5906,9 +8368,16 @@ ], "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", "homepage": "https://symfony.com", - "keywords": ["grapheme", "i18n", "string", "unicode", "utf-8", "utf8"], + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], "support": { - "source": "https://github.com/symfony/string/tree/v6.1.6" + "source": "https://github.com/symfony/string/tree/v8.0.4" }, "funding": [ { @@ -5919,163 +8388,62 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-10-10T09:34:31+00:00" - }, - { - "name": "symplify/autowire-array-parameter", - "version": "10.3.3", - "source": { - "type": "git", - "url": "https://github.com/symplify/autowire-array-parameter.git", - "reference": "e3ca795122712fab224a5c10339b1fb278505420" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symplify/autowire-array-parameter/zipball/e3ca795122712fab224a5c10339b1fb278505420", - "reference": "e3ca795122712fab224a5c10339b1fb278505420", - "shasum": "" - }, - "require": { - "nette/utils": "^3.2", - "php": ">=8.0", - "symfony/dependency-injection": "^6.0", - "symplify/package-builder": "^10.3.3" - }, - "conflict": { - "symplify/astral": "<10.3.3", - "symplify/coding-standard": "<10.3.3", - "symplify/composer-json-manipulator": "<10.3.3", - "symplify/config-transformer": "<10.3.3", - "symplify/easy-ci": "<10.3.3", - "symplify/easy-coding-standard": "<10.3.3", - "symplify/easy-parallel": "<10.3.3", - "symplify/easy-testing": "<10.3.3", - "symplify/monorepo-builder": "<10.3.3", - "symplify/neon-config-dumper": "<10.3.3", - "symplify/php-config-printer": "<10.3.3", - "symplify/phpstan-extensions": "<10.3.3", - "symplify/phpstan-rules": "<10.3.3", - "symplify/rule-doc-generator": "<10.3.3", - "symplify/rule-doc-generator-contracts": "<10.3.3", - "symplify/skipper": "<10.3.3", - "symplify/smart-file-system": "<10.3.3", - "symplify/symfony-static-dumper": "<10.3.3", - "symplify/symplify-kernel": "<10.3.3", - "symplify/vendor-patches": "<10.3.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symplify\\AutowireArrayParameter\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], - "description": "Autowire array parameters for your Symfony applications", - "support": { - "source": "https://github.com/symplify/autowire-array-parameter/tree/10.3.3" - }, - "funding": [ - { - "url": "https://www.paypal.me/rectorphp", - "type": "custom" - }, - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2022-06-13T14:05:31+00:00" + "time": "2026-01-12T12:37:40+00:00" }, { "name": "symplify/coding-standard", - "version": "10.3.3", + "version": "13.0.0", "source": { "type": "git", "url": "https://github.com/symplify/coding-standard.git", - "reference": "07e8a9f67dd74ede6038dc70750654449e80e9ab" + "reference": "bd7c36af1e96eecd08cfcc0a772a19767aa02300" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symplify/coding-standard/zipball/07e8a9f67dd74ede6038dc70750654449e80e9ab", - "reference": "07e8a9f67dd74ede6038dc70750654449e80e9ab", + "url": "https://api.github.com/repos/symplify/coding-standard/zipball/bd7c36af1e96eecd08cfcc0a772a19767aa02300", + "reference": "bd7c36af1e96eecd08cfcc0a772a19767aa02300", "shasum": "" }, "require": { - "friendsofphp/php-cs-fixer": "^3.8", - "nette/utils": "^3.2", - "php": ">=8.0", - "symplify/autowire-array-parameter": "^10.3.3", - "symplify/package-builder": "^10.3.3", - "symplify/rule-doc-generator-contracts": "^10.3.3", - "symplify/symplify-kernel": "^10.3.3" - }, - "conflict": { - "symplify/astral": "<10.3.3", - "symplify/composer-json-manipulator": "<10.3.3", - "symplify/config-transformer": "<10.3.3", - "symplify/easy-ci": "<10.3.3", - "symplify/easy-coding-standard": "<10.3.3", - "symplify/easy-parallel": "<10.3.3", - "symplify/easy-testing": "<10.3.3", - "symplify/monorepo-builder": "<10.3.3", - "symplify/neon-config-dumper": "<10.3.3", - "symplify/php-config-printer": "<10.3.3", - "symplify/phpstan-extensions": "<10.3.3", - "symplify/phpstan-rules": "<10.3.3", - "symplify/rule-doc-generator": "<10.3.3", - "symplify/skipper": "<10.3.3", - "symplify/smart-file-system": "<10.3.3", - "symplify/symfony-static-dumper": "<10.3.3", - "symplify/vendor-patches": "<10.3.3" + "friendsofphp/php-cs-fixer": "^3.89", + "nette/utils": "^4.0", + "php": ">=8.2" }, "require-dev": { - "cweagans/composer-patches": "^1.7", - "doctrine/orm": "^2.10", - "nette/application": "^3.1", - "nette/bootstrap": "^3.1", - "phpunit/phpunit": "^9.5", - "symfony/framework-bundle": "^6.0", - "symplify/easy-coding-standard": "^10.3.3", - "symplify/rule-doc-generator": "^10.3.3", - "symplify/smart-file-system": "^10.3.3", - "symplify/symplify-kernel": "^10.3.3" + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^11.5|^12.0", + "rector/jack": "^0.2", + "rector/rector": "^2.2", + "squizlabs/php_codesniffer": "^4.0", + "symplify/easy-coding-standard": "^12.6", + "symplify/phpstan-extensions": "^12.0", + "tomasvotruba/class-leak": "^2.0", + "tracy/tracy": "^2.11" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.4-dev" - }, - "enable-patching": true, - "patches": { - "symfony/dependency-injection": [ - "https://raw.githubusercontent.com/symplify/vendor-patch-files/main/patches/generic-php-config-loader.patch" - ] - } - }, "autoload": { "psr-4": { "Symplify\\CodingStandard\\": "src" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "description": "Set of Symplify rules for PHP_CodeSniffer and PHP CS Fixer.", "support": { - "source": "https://github.com/symplify/coding-standard/tree/10.3.3" + "issues": "https://github.com/symplify/coding-standard/issues", + "source": "https://github.com/symplify/coding-standard/tree/13.0.0" }, "funding": [ { @@ -6087,121 +8455,56 @@ "type": "github" } ], - "time": "2022-06-13T14:05:35+00:00" - }, - { - "name": "symplify/composer-json-manipulator", - "version": "10.3.3", - "source": { - "type": "git", - "url": "https://github.com/symplify/composer-json-manipulator.git", - "reference": "84f716bd543d946921c4ef6d2197a9f2877c7691" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symplify/composer-json-manipulator/zipball/84f716bd543d946921c4ef6d2197a9f2877c7691", - "reference": "84f716bd543d946921c4ef6d2197a9f2877c7691", - "shasum": "" - }, - "require": { - "nette/utils": "^3.2", - "php": ">=8.0", - "symfony/config": "^6.0", - "symfony/dependency-injection": "^6.0", - "symfony/filesystem": "^6.0", - "symplify/package-builder": "^10.3.3", - "symplify/smart-file-system": "^10.3.3", - "symplify/symplify-kernel": "^10.3.3" - }, - "conflict": { - "symplify/astral": "<10.3.3", - "symplify/autowire-array-parameter": "<10.3.3", - "symplify/coding-standard": "<10.3.3", - "symplify/config-transformer": "<10.3.3", - "symplify/easy-ci": "<10.3.3", - "symplify/easy-coding-standard": "<10.3.3", - "symplify/easy-parallel": "<10.3.3", - "symplify/easy-testing": "<10.3.3", - "symplify/monorepo-builder": "<10.3.3", - "symplify/neon-config-dumper": "<10.3.3", - "symplify/php-config-printer": "<10.3.3", - "symplify/phpstan-extensions": "<10.3.3", - "symplify/phpstan-rules": "<10.3.3", - "symplify/rule-doc-generator": "<10.3.3", - "symplify/rule-doc-generator-contracts": "<10.3.3", - "symplify/skipper": "<10.3.3", - "symplify/symfony-static-dumper": "<10.3.3", - "symplify/symplify-kernel": "<9.4.70", - "symplify/vendor-patches": "<10.3.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.5" - }, - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-main": "10.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symplify\\ComposerJsonManipulator\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], - "description": "Package to load, merge and save composer.json file(s)", - "support": { - "source": "https://github.com/symplify/composer-json-manipulator/tree/10.3.3" - }, - "funding": [ - { - "url": "https://www.paypal.me/rectorphp", - "type": "custom" - }, - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2022-06-13T14:06:01+00:00" + "time": "2025-10-30T21:46:47+00:00" }, { "name": "symplify/easy-coding-standard", - "version": "10.3.3", + "version": "13.0.4", "source": { "type": "git", - "url": "https://github.com/symplify/easy-coding-standard.git", - "reference": "c93878b3c052321231519b6540e227380f90be17" + "url": "https://github.com/easy-coding-standard/easy-coding-standard.git", + "reference": "5c7e7a07e5d6a98b9dd2e6fc0a9155efb7c166c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/c93878b3c052321231519b6540e227380f90be17", - "reference": "c93878b3c052321231519b6540e227380f90be17", + "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/5c7e7a07e5d6a98b9dd2e6fc0a9155efb7c166c8", + "reference": "5c7e7a07e5d6a98b9dd2e6fc0a9155efb7c166c8", "shasum": "" }, "require": { "php": ">=7.2" }, "conflict": { - "friendsofphp/php-cs-fixer": "<3.0", - "squizlabs/php_codesniffer": "<3.6" + "friendsofphp/php-cs-fixer": "<3.92.4", + "phpcsstandards/php_codesniffer": "<4.0.1", + "symplify/coding-standard": "<12.1" }, - "bin": ["bin/ecs"], + "suggest": { + "ext-dom": "Needed to support checkstyle output format in class CheckstyleOutputFormatter" + }, + "bin": [ + "bin/ecs" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.3-dev" - } - }, "autoload": { - "files": ["bootstrap.php"] + "files": [ + "bootstrap.php" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], - "description": "Prefixed scoped version of ECS package", + "license": [ + "MIT" + ], + "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer", + "keywords": [ + "Code style", + "automation", + "fixer", + "static analysis" + ], "support": { - "source": "https://github.com/symplify/easy-coding-standard/tree/10.3.3" + "issues": "https://github.com/easy-coding-standard/easy-coding-standard/issues", + "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/13.0.4" }, "funding": [ { @@ -6213,400 +8516,38 @@ "type": "github" } ], - "time": "2022-06-13T14:03:37+00:00" - }, - { - "name": "symplify/easy-testing", - "version": "10.3.3", - "source": { - "type": "git", - "url": "https://github.com/symplify/easy-testing.git", - "reference": "d4a78c8d55282143754d9be0d9577865394c073c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symplify/easy-testing/zipball/d4a78c8d55282143754d9be0d9577865394c073c", - "reference": "d4a78c8d55282143754d9be0d9577865394c073c", - "shasum": "" - }, - "require": { - "nette/utils": "^3.2", - "php": ">=8.0", - "symfony/console": "^6.0", - "symfony/dependency-injection": "^6.0", - "symfony/finder": "^6.0", - "symplify/package-builder": "^10.3.3", - "symplify/smart-file-system": "^10.3.3", - "symplify/symplify-kernel": "^10.3.3" - }, - "conflict": { - "symplify/astral": "<10.3.3", - "symplify/autowire-array-parameter": "<10.3.3", - "symplify/coding-standard": "<10.3.3", - "symplify/composer-json-manipulator": "<10.3.3", - "symplify/config-transformer": "<10.3.3", - "symplify/easy-ci": "<10.3.3", - "symplify/easy-coding-standard": "<10.3.3", - "symplify/easy-parallel": "<10.3.3", - "symplify/monorepo-builder": "<10.3.3", - "symplify/neon-config-dumper": "<10.3.3", - "symplify/php-config-printer": "<10.3.3", - "symplify/phpstan-extensions": "<10.3.3", - "symplify/phpstan-rules": "<10.3.3", - "symplify/rule-doc-generator": "<10.3.3", - "symplify/rule-doc-generator-contracts": "<10.3.3", - "symplify/skipper": "<10.3.3", - "symplify/symfony-static-dumper": "<10.3.3", - "symplify/vendor-patches": "<10.3.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.5" - }, - "bin": ["bin/easy-testing"], - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-main": "10.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symplify\\EasyTesting\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], - "description": "Testing made easy", - "support": { - "source": "https://github.com/symplify/easy-testing/tree/10.3.3" - }, - "funding": [ - { - "url": "https://www.paypal.me/rectorphp", - "type": "custom" - }, - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2022-06-13T14:05:39+00:00" - }, - { - "name": "symplify/package-builder", - "version": "10.3.3", - "source": { - "type": "git", - "url": "https://github.com/symplify/package-builder.git", - "reference": "bc785e064429f2341d035cc88cc954a56f220040" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symplify/package-builder/zipball/bc785e064429f2341d035cc88cc954a56f220040", - "reference": "bc785e064429f2341d035cc88cc954a56f220040", - "shasum": "" - }, - "require": { - "nette/utils": "^3.2", - "php": ">=8.0", - "sebastian/diff": "^4.0", - "symfony/config": "^6.0", - "symfony/console": "^6.0", - "symfony/dependency-injection": "^6.0", - "symfony/finder": "^6.0", - "symplify/easy-testing": "^10.3.3", - "symplify/symplify-kernel": "^10.3.3" - }, - "conflict": { - "symplify/astral": "<10.3.3", - "symplify/autowire-array-parameter": "<10.3.3", - "symplify/coding-standard": "<10.3.3", - "symplify/composer-json-manipulator": "<10.3.3", - "symplify/config-transformer": "<10.3.3", - "symplify/easy-ci": "<10.3.3", - "symplify/easy-coding-standard": "<10.3.3", - "symplify/easy-parallel": "<10.3.3", - "symplify/monorepo-builder": "<10.3.3", - "symplify/neon-config-dumper": "<10.3.3", - "symplify/php-config-printer": "<10.3.3", - "symplify/phpstan-extensions": "<10.3.3", - "symplify/phpstan-rules": "<10.3.3", - "symplify/rule-doc-generator": "<10.3.3", - "symplify/rule-doc-generator-contracts": "<10.3.3", - "symplify/skipper": "<10.3.3", - "symplify/smart-file-system": "<10.3.3", - "symplify/symfony-static-dumper": "<10.3.3", - "symplify/vendor-patches": "<10.3.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symplify\\PackageBuilder\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], - "description": "Dependency Injection, Console and Kernel toolkit for Symplify packages.", - "support": { - "source": "https://github.com/symplify/package-builder/tree/10.3.3" - }, - "funding": [ - { - "url": "https://www.paypal.me/rectorphp", - "type": "custom" - }, - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2022-06-13T14:05:45+00:00" - }, - { - "name": "symplify/rule-doc-generator-contracts", - "version": "10.3.3", - "source": { - "type": "git", - "url": "https://github.com/symplify/rule-doc-generator-contracts.git", - "reference": "6c5f2661fdd9a290d455b31aa3619c80702119cd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symplify/rule-doc-generator-contracts/zipball/6c5f2661fdd9a290d455b31aa3619c80702119cd", - "reference": "6c5f2661fdd9a290d455b31aa3619c80702119cd", - "shasum": "" - }, - "require": { - "nette/utils": "^3.2", - "php": ">=8.0" - }, - "conflict": { - "symplify/astral": "<10.3.3", - "symplify/autowire-array-parameter": "<10.3.3", - "symplify/coding-standard": "<10.3.3", - "symplify/composer-json-manipulator": "<10.3.3", - "symplify/config-transformer": "<10.3.3", - "symplify/easy-ci": "<10.3.3", - "symplify/easy-coding-standard": "<10.3.3", - "symplify/easy-parallel": "<10.3.3", - "symplify/easy-testing": "<10.3.3", - "symplify/monorepo-builder": "<10.3.3", - "symplify/neon-config-dumper": "<10.3.3", - "symplify/package-builder": "<10.3.3", - "symplify/php-config-printer": "<10.3.3", - "symplify/phpstan-extensions": "<10.3.3", - "symplify/phpstan-rules": "<10.3.3", - "symplify/rule-doc-generator": "<10.3.3", - "symplify/skipper": "<10.3.3", - "symplify/smart-file-system": "<10.3.3", - "symplify/symfony-static-dumper": "<10.3.3", - "symplify/symplify-kernel": "<10.3.3", - "symplify/vendor-patches": "<10.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symplify\\RuleDocGenerator\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], - "description": "Contracts for production code of RuleDocGenerator", - "support": { - "source": "https://github.com/symplify/rule-doc-generator-contracts/tree/10.3.3" - }, - "funding": [ - { - "url": "https://www.paypal.me/rectorphp", - "type": "custom" - }, - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2022-06-13T14:03:35+00:00" - }, - { - "name": "symplify/smart-file-system", - "version": "10.3.3", - "source": { - "type": "git", - "url": "https://github.com/symplify/smart-file-system.git", - "reference": "0b465fcf7490ac89708510551a044961b9124493" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symplify/smart-file-system/zipball/0b465fcf7490ac89708510551a044961b9124493", - "reference": "0b465fcf7490ac89708510551a044961b9124493", - "shasum": "" - }, - "require": { - "nette/utils": "^3.2", - "php": ">=8.0", - "symfony/filesystem": "^6.0", - "symfony/finder": "^6.0" - }, - "conflict": { - "symplify/astral": "<10.3.3", - "symplify/autowire-array-parameter": "<10.3.3", - "symplify/coding-standard": "<10.3.3", - "symplify/composer-json-manipulator": "<10.3.3", - "symplify/config-transformer": "<10.3.3", - "symplify/easy-ci": "<10.3.3", - "symplify/easy-coding-standard": "<10.3.3", - "symplify/easy-parallel": "<10.3.3", - "symplify/easy-testing": "<10.3.3", - "symplify/monorepo-builder": "<10.3.3", - "symplify/neon-config-dumper": "<10.3.3", - "symplify/package-builder": "<10.3.3", - "symplify/php-config-printer": "<10.3.3", - "symplify/phpstan-extensions": "<10.3.3", - "symplify/phpstan-rules": "<10.3.3", - "symplify/rule-doc-generator": "<10.3.3", - "symplify/rule-doc-generator-contracts": "<10.3.3", - "symplify/skipper": "<10.3.3", - "symplify/symfony-static-dumper": "<10.3.3", - "symplify/symplify-kernel": "<10.3.3", - "symplify/vendor-patches": "<10.3.3" - }, - "require-dev": { - "nette/finder": "^2.5", - "phpunit/phpunit": "^9.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symplify\\SmartFileSystem\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], - "description": "Sanitized FileInfo with safe getRealPath() and other handy methods", - "support": { - "source": "https://github.com/symplify/smart-file-system/tree/10.3.3" - }, - "funding": [ - { - "url": "https://www.paypal.me/rectorphp", - "type": "custom" - }, - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2022-06-13T14:03:57+00:00" - }, - { - "name": "symplify/symplify-kernel", - "version": "10.3.3", - "source": { - "type": "git", - "url": "https://github.com/symplify/symplify-kernel.git", - "reference": "951838b8c4cee31c347a132755428c39437585c8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symplify/symplify-kernel/zipball/951838b8c4cee31c347a132755428c39437585c8", - "reference": "951838b8c4cee31c347a132755428c39437585c8", - "shasum": "" - }, - "require": { - "php": ">=8.0", - "symfony/console": "^6.0", - "symfony/dependency-injection": "^6.0", - "symplify/autowire-array-parameter": "^10.3.3", - "symplify/composer-json-manipulator": "^10.3.3", - "symplify/package-builder": "^10.3.3", - "symplify/smart-file-system": "^10.3.3", - "webmozart/assert": "^1.10" - }, - "conflict": { - "symplify/astral": "<10.3.3", - "symplify/coding-standard": "<10.3.3", - "symplify/config-transformer": "<10.3.3", - "symplify/easy-ci": "<10.3.3", - "symplify/easy-coding-standard": "<10.3.3", - "symplify/easy-parallel": "<10.3.3", - "symplify/easy-testing": "<10.3.3", - "symplify/monorepo-builder": "<10.3.3", - "symplify/neon-config-dumper": "<10.3.3", - "symplify/php-config-printer": "<10.3.3", - "symplify/phpstan-extensions": "<10.3.3", - "symplify/phpstan-rules": "<10.3.3", - "symplify/rule-doc-generator": "<10.3.3", - "symplify/rule-doc-generator-contracts": "<10.3.3", - "symplify/skipper": "<10.3.3", - "symplify/symfony-static-dumper": "<10.3.3", - "symplify/vendor-patches": "<10.3.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symplify\\SymplifyKernel\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], - "description": "Internal Kernel for Symplify packages", - "support": { - "source": "https://github.com/symplify/symplify-kernel/tree/10.3.3" - }, - "time": "2022-06-13T14:06:24+00:00" + "time": "2026-01-05T09:10:04+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/7989e43bf381af0eac72e4f0ca5bcbfa81658be4", + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4", "shasum": "" }, "require": { "ext-dom": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" + "php": "^8.1" }, "type": "library", "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Arne Blankerts", @@ -6617,7 +8558,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/2.0.1" }, "funding": [ { @@ -6625,73 +8566,22 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "php": "^7.2 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": ["assert", "check", "validate"], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" - }, - "time": "2022-06-03T18:03:27+00:00" + "time": "2025-12-08T11:19:18+00:00" } ], "aliases": [], - "minimum-stability": "stable", + "minimum-stability": "dev", "stability-flags": { - "james-heinrich/getid3": 20, - "myth/auth": 20, - "michalsn/codeigniter4-uuid": 20 + "adaures/castopod-plugins-manager": 20, + "codeigniter4/tasks": 20, + "opawg/user-agents-v2-php": 20, + "yassinedoghri/podcast-feed": 20 }, "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^8.0" + "php": "^8.4" }, - "platform-dev": [], - "plugin-api-version": "2.3.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/crontab b/crontab deleted file mode 100644 index 3589b3e9..00000000 --- a/crontab +++ /dev/null @@ -1,3 +0,0 @@ -* * * * * /usr/local/bin/php /castopod/public/index.php scheduled-activities -* * * * * /usr/local/bin/php /castopod/public/index.php scheduled-video-clips -* * * * * /usr/local/bin/php /castopod/public/index.php scheduled-websub-publish diff --git a/crowdin.yml b/crowdin.yml index 7f46417d..944c6241 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -4,7 +4,5 @@ files: translation: /app/Language/%osx_locale%/%original_file_name% - source: /modules/**/Language/en/*.php translation: /modules/**/Language/%osx_locale%/%original_file_name% - - source: /docs/src/getting-started/*.md - translation: /docs/src/%osx_locale%/getting-started/%original_file_name% - - source: /docs/src/index.md - translation: /docs/src/%osx_locale%/index.md + - source: /docs/src/content/docs/en/**/*.mdx + translation: /docs/src/content/docs/%osx_locale%/**/%original_file_name% diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 52a712c7..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,67 +0,0 @@ -version: "3" - -networks: - castopod: - -services: - app: - build: - context: . - dockerfile: docker/development/Dockerfile - container_name: app - command: /bin/sh -c "crontab ./crontab && cron && service cron reload && php spark serve - 0.0.0.0" - ports: - - 8080:8080 - volumes: - - .:/castopod - depends_on: - - redis - - mariadb - networks: - - castopod - - redis: - image: redis:alpine - container_name: castopod_redis - ports: - - 6379:6379 - volumes: - - redis:/data - networks: - - castopod - - mariadb: - image: mariadb:10.2 - container_name: castopod_mariadb - ports: - - 3306:3306 - volumes: - - ./initdb:/docker-entrypoint-initdb.d - - mariadb:/var/lib/mysql - environment: - MYSQL_ROOT_PASSWORD: root - MYSQL_DATABASE: castopod - MYSQL_USER: castopod - MYSQL_PASSWORD: castopod - networks: - - castopod - - phpmyadmin: - image: phpmyadmin/phpmyadmin:latest - container_name: castopod_phpmyadmin - environment: - PMA_HOST: mariadb - PMA_PORT: 3306 - ports: - - 8888:80 - volumes: - - phpmyadmin:/sessions - depends_on: - - mariadb - networks: - - castopod - -volumes: - redis: - mariadb: - phpmyadmin: diff --git a/docker/ci/Dockerfile b/docker/ci/Dockerfile new file mode 100644 index 00000000..bae95838 --- /dev/null +++ b/docker/ci/Dockerfile @@ -0,0 +1,40 @@ +#################################################### +# Castopod CI/CD docker file +#################################################### +# ⚠️ NOT optimized for production +# should be used only for continuous integration +#--------------------------------------------------- +FROM php:8.5-fpm-alpine3.23 + +LABEL maintainer="Yassine Doghri " + +RUN \ + # install composer + curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer \ + # install ci requirements + && apk add --no-cache \ + nodejs \ + # install npm for @semantic-release/npm + npm \ + git \ + unzip \ + wget \ + jq \ + zip \ + openssh-client \ + rsync \ + icu-libs \ + mysql \ + mysql-client \ + && apk add --no-cache --virtual .php-ext-build-dep icu-dev \ + && docker-php-ext-install \ + intl \ + mysqli \ + && apk del .php-ext-build-dep \ + # install pnpm + && npm install --global corepack@latest \ + && corepack enable pnpm \ + # set pnpm store directory + && pnpm config set store-dir .pnpm-store \ + # set composer cache directory + && composer config -g cache-dir .composer-cache diff --git a/docker/production/.gitlab-ci.yml b/docker/production/.gitlab-ci.yml index 2a8c05e7..6256cc13 100644 --- a/docker/production/.gitlab-ci.yml +++ b/docker/production/.gitlab-ci.yml @@ -4,54 +4,109 @@ stages: docker-build-rolling: stage: build image: - name: gcr.io/kaniko-project/executor:debug - entrypoint: [""] + name: docker.io/docker:29.2-dind + services: + - docker:29.2-dind variables: TAG: $CI_COMMIT_BRANCH - script: - - cp ${DOCKER_HUB_CONFIG} /kaniko/.docker/config.json - - /kaniko/executor --context . --dockerfile docker/production/web-server/Dockerfile --destination ${DOCKER_IMAGE_WEB_SERVER}:${TAG} - - /kaniko/executor --context . --dockerfile docker/production/app/Dockerfile --destination ${DOCKER_IMAGE_APP}:${TAG} - needs: - - pipeline: $PARENT_PIPELINE_ID - job: bundle - only: - refs: - - develop + DOCKER_BUILDKIT: 1 + DOCKER_HOST: tcp://docker:2376 + DOCKER_TLS_CERTDIR: "/certs" + before_script: + # ensure the Docker config directory exists + - mkdir -p /root/.docker + # copy credentials to authenticate against registry + - cp ${DOCKER_HUB_CONFIG} /root/.docker/config.json -docker-build-main-release: + - docker context create tls-environment + + # Create and use builder with optimized settings + - docker buildx create + --name fast-multiplatform + --driver docker-container + --driver-opt network=host + --driver-opt image=moby/buildkit:v0.27.1 + --use + tls-environment + + # initialize and boot fast-multiplatform builder + # configure BuildKit features that aren't enabled by default + - docker buildx inspect --bootstrap + script: + - docker buildx build + --target production + --secret id=maxmind-licence-key,env=MAXMIND_LICENCE_KEY + --platform linux/amd64 + --file docker/production/Dockerfile + --push + --tag ${DOCKER_IMAGE_CASTOPOD}:${TAG} + . + rules: + - if: $CI_COMMIT_BRANCH == 'develop' + +docker-build-release: stage: build image: - name: gcr.io/kaniko-project/executor:debug - entrypoint: [""] - script: - - cp ${DOCKER_HUB_CONFIG} /kaniko/.docker/config.json - - export CP_VERSION=$(cat CP_VERSION.env) - - /kaniko/executor --context . --dockerfile docker/production/web-server/Dockerfile --destination ${DOCKER_IMAGE_WEB_SERVER}:${CP_VERSION} --destination ${DOCKER_IMAGE_WEB_SERVER}:latest - - /kaniko/executor --context . --dockerfile docker/production/app/Dockerfile --destination ${DOCKER_IMAGE_APP}:${CP_VERSION} --destination ${DOCKER_IMAGE_APP}:latest - needs: - - pipeline: $PARENT_PIPELINE_ID - job: release - only: - refs: - - main - -docker-build-alpha-beta-release: - stage: build - image: - name: gcr.io/kaniko-project/executor:debug - entrypoint: [""] + name: docker.io/docker:29.2-dind + services: + - docker:29.2-dind variables: - TAG: $CI_COMMIT_BRANCH + DOCKER_BUILDKIT: 1 + DOCKER_HOST: tcp://docker:2376 + DOCKER_TLS_CERTDIR: "/certs" + before_script: + # ensure the Docker config directory exists + - mkdir -p /root/.docker + # copy credentials to authenticate against registry + - cp ${DOCKER_HUB_CONFIG} /root/.docker/config.json + + ## Prepare Docker image tags from git tag + ## -------------------------------------- + # extract full SemVer from git tag (remove leading "v") + - export IMAGE_TAG_VERSION=$(echo "$CI_COMMIT_TAG" | sed 's/^v//') + # extract channel (prerelease like "alpha", "beta", "next"; "latest" for stable) + - export IMAGE_TAG_CHANNEL=$(echo "$IMAGE_TAG_VERSION" | sed 's/^[^-]*-\([^.]*\)\..*/\1/; t; s/.*/latest/') + # extract major version number (first SemVer component) + - export IMAGE_TAG_MAJOR=$(echo "$IMAGE_TAG_VERSION" | sed 's/\..*//') + # construct major-channel tag ("X" for stable, "X-channel" for prerelease) + - export IMAGE_TAG_MAJOR_CHANNEL=$([ "$IMAGE_TAG_CHANNEL" = "latest" ] && echo "$IMAGE_TAG_MAJOR" || echo "${IMAGE_TAG_MAJOR}-${IMAGE_TAG_CHANNEL}") + + - docker context create tls-environment + + # Create and use builder with optimized settings + - docker buildx create + --name fast-multiplatform + --driver docker-container + --driver-opt network=host + --driver-opt image=moby/buildkit:v0.27.1 + --use + tls-environment + + # initialize and boot fast-multiplatform builder + # configure BuildKit features that aren't enabled by default + - docker buildx inspect --bootstrap script: - - cp ${DOCKER_HUB_CONFIG} /kaniko/.docker/config.json - - export CP_VERSION=$(cat CP_VERSION.env) - - /kaniko/executor --context . --dockerfile docker/production/web-server/Dockerfile --destination ${DOCKER_IMAGE_WEB_SERVER}:${CP_VERSION} --destination ${DOCKER_IMAGE_WEB_SERVER}:${TAG} - - /kaniko/executor --context . --dockerfile docker/production/app/Dockerfile --destination ${DOCKER_IMAGE_APP}:${CP_VERSION} --destination ${DOCKER_IMAGE_APP}:${TAG} - needs: - - pipeline: $PARENT_PIPELINE_ID - job: release - only: - refs: - - alpha - - beta + - docker buildx build + --target production + --secret id=maxmind-licence-key,env=MAXMIND_LICENCE_KEY + --platform linux/amd64 + --file docker/production/Dockerfile + --push + --tag ${DOCKER_IMAGE_CASTOPOD}:${IMAGE_TAG_VERSION} + --tag ${DOCKER_IMAGE_CASTOPOD}:${IMAGE_TAG_CHANNEL} + --tag ${DOCKER_IMAGE_CASTOPOD}:${IMAGE_TAG_MAJOR_CHANNEL} + . + # when --platform=linux/amd64,linux/arm64: amd64 image takes too long to be pushed as it needs to wait for arm64 to be built + # --> build and push amd64 image first, then overwrite manifest after building arm64 + - docker buildx build + --target production + --secret id=maxmind-licence-key,env=MAXMIND_LICENCE_KEY + --platform linux/amd64,linux/arm64 + --file docker/production/Dockerfile + --push + --tag ${DOCKER_IMAGE_CASTOPOD}:${IMAGE_TAG_VERSION} + --tag ${DOCKER_IMAGE_CASTOPOD}:${IMAGE_TAG_CHANNEL} + --tag ${DOCKER_IMAGE_CASTOPOD}:${IMAGE_TAG_MAJOR_CHANNEL} + . + rules: + - if: $CI_COMMIT_TAG diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile new file mode 100644 index 00000000..f55d3cfc --- /dev/null +++ b/docker/production/Dockerfile @@ -0,0 +1,154 @@ +#################################################### +# Castopod's Production Dockerfile +#################################################### +# An optimized Dockerfile for production using +# multi-stage builds: +# 1. BUNDLE castopod +# 2. BUILD the FrankenPHP/debian based prod image +#--------------------------------------------------- + +ARG PHP_VERSION="8.5" + +#################################################### +# BUNDLE STAGE +# ------------------------------------------------- +# Bundle castopod for production using +# a PHP / Alpine image +#--------------------------------------------------- +FROM php:${PHP_VERSION}-alpine3.23 AS bundle + +LABEL maintainer="Yassine Doghri " + +COPY . /castopod-src +WORKDIR /castopod-src + +COPY --from=composer:2.9 /usr/bin/composer /usr/local/bin/composer + +RUN \ + # download GeoLite2-City archive and extract it to writable/uploads + --mount=type=secret,id=maxmind-licence-key,env=MAXMIND_LICENCE_KEY \ + wget -c "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=$MAXMIND_LICENCE_KEY&suffix=tar.gz" -O - | tar -xz -C ./writable/uploads/ \ + # rename extracted archives' folders + && mv ./writable/uploads/GeoLite2-City* ./writable/uploads/GeoLite2-City + +RUN \ + # install composer globally + curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \ + # install node and pnpm + && apk add --no-cache \ + nodejs \ + pnpm \ + git \ + rsync \ + # install production dependencies only using the --no-dev option + && composer install --no-dev --prefer-dist --no-ansi --no-interaction --no-progress --ignore-platform-reqs \ + # install js dependencies based on lockfile + && pnpm install --frozen-lockfile \ + # build all production static assets (css, js, images, icons, fonts, etc.) + && pnpm run build \ + # create castopod folder bundle: uses .rsync-filter (-F) file to copy only needed files + && rsync -aF . /castopod + + +#################################################### +# BUILD STAGE +# ------------------------------------------------- +# Define production image based on FrankenPHP / +# Debian with services managed by s6-overlay +#--------------------------------------------------- +FROM serversideup/php:${PHP_VERSION}-frankenphp-trixie AS production + +LABEL maintainer="Yassine Doghri " + +USER root + +ARG TARGETARCH + +# Latest releases available at https://github.com/aptible/supercronic/releases +# add supercronic to handle cron jobs +RUN if [ "$TARGETARCH" = "amd64" ]; then \ + SUPERCRONIC_URL="https://github.com/aptible/supercronic/releases/download/v0.2.43/supercronic-linux-amd64"; \ + SUPERCRONIC_SHA1SUM="f97b92132b61a8f827c3faf67106dc0e4467ccf2"; \ + SUPERCRONIC="supercronic-linux-amd64"; \ + elif [ "$TARGETARCH" = "arm64" ]; then \ + SUPERCRONIC_URL="https://github.com/aptible/supercronic/releases/download/v0.2.43/supercronic-linux-arm64"; \ + SUPERCRONIC_SHA1SUM="5c6266786c2813d6f8a99965d84452faae42b483"; \ + SUPERCRONIC="supercronic-linux-arm64"; \ + else \ + echo "Unsupported TARGETARCH: $TARGETARCH"; exit 1; \ + fi && \ + curl -fsSLO "$SUPERCRONIC_URL" \ + && echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c - \ + && chmod +x "$SUPERCRONIC" \ + && mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" \ + && ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic + +ARG S6_OVERLAY_VERSION=3.2.2.0 + +# add s6-overlay process manager +ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp +RUN tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz + +# add Arch-specific tarball +RUN if [ "$TARGETARCH" = "amd64" ]; then \ + S6_ARCH="x86_64"; \ + elif [ "$TARGETARCH" = "arm64" ]; then \ + S6_ARCH="aarch64"; \ + else \ + echo "Unsupported TARGETARCH: $TARGETARCH"; exit 1; \ + fi && \ + curl -fsSL -o /tmp/s6-overlay-${S6_ARCH}.tar.xz \ + "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz" && \ + tar -C / -Jxpf /tmp/s6-overlay-${S6_ARCH}.tar.xz && \ + rm /tmp/s6-overlay-${S6_ARCH}.tar.xz + +# copy s6-overlay services +COPY --chown=www-data:www-data docker/production/s6-rc.d /etc/s6-overlay/s6-rc.d + +# make prepare-environment executable for bootstrapping the Castopod environment +RUN chmod +x /etc/s6-overlay/s6-rc.d/bootstrap/prepare-environment.sh + +RUN \ + apt-get update \ + && apt-get install -y \ + ffmpeg \ + libfreetype6-dev \ + libjpeg62-turbo-dev \ + libpng-dev \ + libwebp-dev \ + libicu-dev \ + && install-php-extensions \ + intl \ + mysqli \ + exif \ + gd \ + # As of PHP 7.4 we don't need to add --with-png + && docker-php-ext-configure gd --with-webp --with-jpeg --with-freetype + +# copy castopod bundle from bundle stage +COPY --from=bundle --chown=www-data:www-data /castopod /app + +RUN \ + chmod -R 550 /app/ \ + && chmod -R 770 /app/public/media/ \ + && chmod -R 770 /app/writable/ \ + && chmod 750 /app/ + +ARG \ + PHP_MEMORY_LIMIT=512M \ + PHP_MAX_EXECUTION_TIME=300 \ + PHP_UPLOAD_MAX_FILE_SIZE=512M \ + PHP_POST_MAX_SIZE=512M \ + PHP_OPCACHE_ENABLE=1 + +ENV \ + PHP_MEMORY_LIMIT=${PHP_MEMORY_LIMIT} \ + PHP_MAX_EXECUTION_TIME=${PHP_MAX_EXECUTION_TIME} \ + PHP_UPLOAD_MAX_FILE_SIZE=${PHP_UPLOAD_MAX_FILE_SIZE} \ + PHP_POST_MAX_SIZE=${PHP_POST_MAX_SIZE} \ + PHP_OPCACHE_ENABLE=${PHP_OPCACHE_ENABLE} + +USER www-data + +ENTRYPOINT ["docker-php-serversideup-entrypoint"] +CMD ["/init"] diff --git a/docker/production/app/Dockerfile b/docker/production/app/Dockerfile deleted file mode 100644 index 6ac15b24..00000000 --- a/docker/production/app/Dockerfile +++ /dev/null @@ -1,40 +0,0 @@ -FROM docker.io/alpine:3.13 AS ffmpeg-downloader - -RUN apk add --no-cache curl && \ - curl https://johnvansickle.com/ffmpeg/releases/ffmpeg-5.1.1-amd64-static.tar.xz -o ffmpeg.tar.xz && \ - mkdir ffmpeg && \ - tar -xJf ffmpeg.tar.xz -C ffmpeg --strip-components 1 - -FROM docker.io/php:8.0-fpm-alpine3.13 - -COPY docker/production/app/entrypoint.sh /entrypoint.sh - -COPY docker/production/app/uploads.ini /usr/local/etc/php/conf.d/uploads.ini - -RUN echo "* * * * * /usr/local/bin/php /opt/castopod/public/index.php scheduled-activities" > /crontab.txt && \ - echo "* * * * 10 /usr/local/bin/php /opt/castopod/public/index.php scheduled-video-clips" >> /crontab.txt && \ - echo "* * * * * /usr/local/bin/php /opt/castopod/public/index.php scheduled-websub-publish" >> /crontab.txt - -RUN apk add --no-cache libpng icu-libs freetype libwebp libjpeg-turbo libxpm ffmpeg && \ - apk add --no-cache --virtual .php-ext-build-dep freetype-dev libpng-dev libjpeg-turbo-dev libwebp-dev zlib-dev libxpm-dev icu-dev && \ - docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp --with-xpm && \ - docker-php-ext-install gd intl mysqli exif && \ - docker-php-ext-enable mysqli gd intl exif && \ - apk del .php-ext-build-dep - -COPY castopod /opt/castopod -COPY --from=ffmpeg-downloader /ffmpeg/ffmpeg /ffmpeg/ffprobe /ffmpeg/qt-faststart /usr/local/bin/ - -RUN chmod 544 /entrypoint.sh && \ - chmod 444 /crontab.txt && \ - /usr/bin/crontab /crontab.txt - -WORKDIR /opt/castopod - -VOLUME /opt/castopod/public/media - -EXPOSE 9000 - -ENTRYPOINT [ "sh", "-c" ] - -CMD [ "/entrypoint.sh" ] diff --git a/docker/production/app/entrypoint.sh b/docker/production/app/entrypoint.sh deleted file mode 100644 index 7246b533..00000000 --- a/docker/production/app/entrypoint.sh +++ /dev/null @@ -1,198 +0,0 @@ -#!/bin/sh - -if [ -z "${CP_BASEURL}" ] -then - echo "CP_BASEURL must be set" - exit 1 -fi - -if [ -z "${CP_MEDIA_BASEURL}" ] -then - echo "CP_MEDIA_BASEURL is empty, leaving empty by default" -fi - -if [ -z "${CP_ADMIN_GATEWAY}" ] -then - echo "CP_ADMIN_GATEWAY is empty, using default" - CP_ADMIN_GATEWAY="cp-admin" -fi - -if [ -z "${CP_AUTH_GATEWAY}" ] -then - echo "CP_AUTH_GATEWAY is empty, using default" - CP_AUTH_GATEWAY="cp-auth" -fi - -if [ -z "${CP_ANALYTICS_SALT}" ] -then - echo "CP_ANALYTICS_SALT is empty, this is mandatory, generate a new one with tr -dc \\!\\#-\\&\\(-\\[\\]-\\_a-\\~ /opt/castopod/.env -app.baseURL="${CP_BASEURL}" -app.mediaBaseURL="${CP_MEDIA_BASEURL}" -EOF - -if [ "${CP_DISABLE_HTTPS}" == "1" ] -then - echo "HTTPS redirection is disabled for test purpose, please enable it in production mode" - echo "app.forceGlobalSecureRequests=false" >> /opt/castopod/.env -else - echo "HTTPS redirection is enabled by default (mandatory to federate with the fediverse), use CP_DISABLE_HTTPS=1 to disable it for testing purposes" -fi - -cat << EOF >> /opt/castopod/.env -admin.gateway="${CP_ADMIN_GATEWAY}" -auth.gateway="${CP_AUTH_GATEWAY}" - -analytics.salt="${CP_ANALYTICS_SALT}" - -database.default.hostname="${CP_DATABASE_HOSTNAME}" -database.default.database="${CP_DATABASE_NAME}" -database.default.username="${CP_DATABASE_USERNAME}" -database.default.password="${CP_DATABASE_PASSWORD}" -database.default.DBPrefix="${CP_DATABASE_PREFIX}" - -cache.handler="${CP_CACHE_HANDLER}" -EOF - -if [ "${CP_CACHE_HANDLER}" == "redis" ] -then - cat << EOF >> /opt/castopod/.env -cache.redis.host="${CP_REDIS_HOST}" -cache.redis.password=${CP_REDIS_PASSWORD} -cache.redis.port=${CP_REDIS_PORT} -cache.redis.database=${CP_REDIS_DATABASE} -EOF -fi - -if [ ! -z "${CP_EMAIL_SMTP_HOST}" ] -then - if [ -z "${CP_EMAIL_SMTP_USERNAME}" ] - then - echo "When CP_EMAIL_SMTP_HOST is provided, CP_EMAIL_SMTP_USERNAME must be set" - exit 1 - fi - - if [ -z "${CP_EMAIL_SMTP_PASSWORD}" ] - then - echo "When CP_EMAIL_SMTP_HOST is provided, CP_EMAIL_SMTP_PASSWORD must be set" - exit 1 - fi - - if [ -z "${CP_EMAIL_FROM}" ] - then - echo "When CP_EMAIL_SMTP_HOST is provided, CP_EMAIL_FROM must be set" - exit 1 - fi - - cat << EOF >> /opt/castopod/.env -email.protocol="smtp" -email.SMTPHost="${CP_EMAIL_SMTP_HOST}" -email.SMTPUser=${CP_EMAIL_SMTP_USERNAME} -email.SMTPPass=${CP_EMAIL_SMTP_PASSWORD} -email.fromEmail=${CP_EMAIL_FROM} -EOF - - if [ ! -z "${CP_EMAIL_SMTP_PORT}" ] - then - cat << EOF >> /opt/castopod/.env -email.SMTPPort=${CP_EMAIL_SMTP_PORT} -EOF - fi - - if [ ! -z "${CP_EMAIL_SMTP_CRYPTO}" ] - then - if [ "${CP_EMAIL_SMTP_CRYPTO}" != "ssl" ] && [ "${CP_EMAIL_SMTP_CRYPTO}" != "tls" ] - then - echo "CP_EMAIL_SMTP_CRYPTO must be ssl or tls" - exit 1 - fi - cat << EOF >> /opt/castopod/.env -email.SMTPCrypto=${CP_EMAIL_SMTP_CRYPTO} -EOF - fi -fi - -echo "Using config:" -cat /opt/castopod/.env - -/usr/sbin/crond -f /crontab.txt -L /dev/stdout & -/usr/local/sbin/php-fpm diff --git a/modules/Auth/Database/Seeds/.gitkeep b/docker/production/s6-rc.d/bootstrap/dependencies.d/frankenphp similarity index 100% rename from modules/Auth/Database/Seeds/.gitkeep rename to docker/production/s6-rc.d/bootstrap/dependencies.d/frankenphp diff --git a/docker/production/s6-rc.d/bootstrap/prepare-environment.sh b/docker/production/s6-rc.d/bootstrap/prepare-environment.sh new file mode 100644 index 00000000..bb5a9a36 --- /dev/null +++ b/docker/production/s6-rc.d/bootstrap/prepare-environment.sh @@ -0,0 +1,277 @@ +#!/command/with-contenv sh + +ENV_FILE_LOCATION=/app/.env + +log_error() { + printf "\033[0;31mERROR:\033[0m $1\n" + exit 1 +} + +log_warning() { + printf "\033[0;33mWARNING:\033[0m $1\n" +} + +log_info() { + printf "\033[0;34mINFO:\033[0m $1\n" +} + +# Remove .env file if exists to recreate it. +rm -f $ENV_FILE_LOCATION + +if [ -z "${CP_BASEURL}" ] +then + log_error "CP_BASEURL must be set" +fi + +if [ -z "${CP_MEDIA_BASEURL}" ] +then + log_info "CP_MEDIA_BASEURL is empty, using CP_BASEURL by default" + CP_MEDIA_BASEURL=$CP_BASEURL +fi + +if [ -z "${CP_ADMIN_GATEWAY}" ] +then + log_info "CP_ADMIN_GATEWAY is empty, using default \"cp-admin\"" + CP_ADMIN_GATEWAY="cp-admin" +fi + +if [ -z "${CP_AUTH_GATEWAY}" ] +then + log_info "CP_AUTH_GATEWAY is empty, using default \"cp-auth\"" + CP_AUTH_GATEWAY="cp-auth" +fi + +if [ -z "${CP_ANALYTICS_SALT}" ] +then + log_error "CP_ANALYTICS_SALT is empty, this is mandatory, generate a new one with tr -dc \\!\\#-\\&\\(-\\[\\]-\\_a-\\~ $ENV_FILE_LOCATION +app.baseURL="${CP_BASEURL}" +media.baseURL="${CP_MEDIA_BASEURL}" +EOF + +if [ "${CP_DISABLE_HTTPS}" = "1" ] +then + log_warning "HTTPS redirection is disabled for testing purposes, please enable it in production mode" + echo "app.forceGlobalSecureRequests=false" >> $ENV_FILE_LOCATION +else + echo "HTTPS redirection is enabled by default (mandatory to federate with the fediverse), use CP_DISABLE_HTTPS=1 to disable it for testing purposes" +fi + +cat << EOF >> $ENV_FILE_LOCATION +admin.gateway="${CP_ADMIN_GATEWAY}" +auth.gateway="${CP_AUTH_GATEWAY}" + +analytics.salt="${CP_ANALYTICS_SALT}" + +database.default.hostname="${CP_DATABASE_HOSTNAME}" +database.default.database="${CP_DATABASE_NAME}" +database.default.username="${CP_DATABASE_USERNAME}" +database.default.password="${CP_DATABASE_PASSWORD}" +database.default.DBPrefix="${CP_DATABASE_PREFIX}" + +cache.handler="${CP_CACHE_HANDLER}" +EOF + +if [ "${CP_CACHE_HANDLER}" = "redis" ] +then + cat << EOF >> $ENV_FILE_LOCATION +cache.redis.host="${CP_REDIS_HOST}" +cache.redis.password=${CP_REDIS_PASSWORD} +cache.redis.port=${CP_REDIS_PORT} +cache.redis.database=${CP_REDIS_DATABASE} +EOF +fi + +if [ "${CP_ENABLE_2FA}" = "true" ] +then + cat << EOF >> $ENV_FILE_LOCATION +auth.enable2FA=true +EOF +fi + +if [ "${CP_MEDIA_FILE_MANAGER}" = "s3" ] +then + cat << EOF >> $ENV_FILE_LOCATION +media.fileManager=s3 +media.s3.endpoint=${CP_MEDIA_S3_ENDPOINT} +media.s3.key=${CP_MEDIA_S3_KEY} +media.s3.secret=${CP_MEDIA_S3_SECRET} +media.s3.region=${CP_MEDIA_S3_REGION} +media.s3.bucket=${CP_MEDIA_S3_BUCKET} +EOF +fi + +if [ ! -z "${CP_MEDIA_S3_PROTOCOL}" ] +then + cat << EOF >> $ENV_FILE_LOCATION +media.s3.protocol=${CP_MEDIA_S3_PROTOCOL} +EOF +fi + +if [ ! -z "${CP_MEDIA_S3_PATH_STYLE_ENDPOINT}" ] +then + cat << EOF >> $ENV_FILE_LOCATION +media.s3.pathStyleEndpoint=${CP_MEDIA_S3_PATH_STYLE_ENDPOINT} +EOF +fi + +if [ ! -z "${CP_MEDIA_S3_KEY_PREFIX}" ] +then + cat << EOF >> $ENV_FILE_LOCATION +media.s3.keyPrefix=${CP_MEDIA_S3_KEY_PREFIX} +EOF +fi + +if [ ! -z "${CP_EMAIL_SMTP_HOST}" ] +then + if [ -z "${CP_EMAIL_SMTP_USERNAME}" ] + then + log_error "When CP_EMAIL_SMTP_HOST is provided, CP_EMAIL_SMTP_USERNAME must be set" + fi + + if [ -z "${CP_EMAIL_SMTP_PASSWORD}" ] + then + log_error "When CP_EMAIL_SMTP_HOST is provided, CP_EMAIL_SMTP_PASSWORD must be set" + fi + + if [ -z "${CP_EMAIL_FROM}" ] + then + log_error "When CP_EMAIL_SMTP_HOST is provided, CP_EMAIL_FROM must be set" + fi + + cat << EOF >> $ENV_FILE_LOCATION +email.protocol="smtp" +email.SMTPHost="${CP_EMAIL_SMTP_HOST}" +email.SMTPUser=${CP_EMAIL_SMTP_USERNAME} +email.SMTPPass=${CP_EMAIL_SMTP_PASSWORD} +email.fromEmail=${CP_EMAIL_FROM} +EOF + + if [ ! -z "${CP_EMAIL_SMTP_PORT}" ] + then + cat << EOF >> $ENV_FILE_LOCATION +email.SMTPPort=${CP_EMAIL_SMTP_PORT} +EOF + fi + + if [ ! -z "${CP_EMAIL_SMTP_CRYPTO}" ] + then + if [ "${CP_EMAIL_SMTP_CRYPTO}" != "ssl" ] && [ "${CP_EMAIL_SMTP_CRYPTO}" != "tls" ] + then + log_error "CP_EMAIL_SMTP_CRYPTO must be ssl or tls" + fi + cat << EOF >> $ENV_FILE_LOCATION +email.SMTPCrypto=${CP_EMAIL_SMTP_CRYPTO} +EOF + fi +fi + +log_info "Using config:" +cat $ENV_FILE_LOCATION + +# prevent .env from being writable +chmod -w $ENV_FILE_LOCATION + +#Run database migrations +/usr/local/bin/php /var/www/html/spark castopod:database-update + +# clear cache to account for new assets and any change in data structure +/usr/local/bin/php /var/www/html/spark cache:clear diff --git a/docker/production/s6-rc.d/bootstrap/type b/docker/production/s6-rc.d/bootstrap/type new file mode 100644 index 00000000..bdd22a18 --- /dev/null +++ b/docker/production/s6-rc.d/bootstrap/type @@ -0,0 +1 @@ +oneshot diff --git a/docker/production/s6-rc.d/bootstrap/up b/docker/production/s6-rc.d/bootstrap/up new file mode 100644 index 00000000..4abab1a4 --- /dev/null +++ b/docker/production/s6-rc.d/bootstrap/up @@ -0,0 +1,2 @@ +#!/command/with-contenv sh +/etc/s6-overlay/s6-rc.d/bootstrap/prepare-environment.sh diff --git a/docker/production/s6-rc.d/frankenphp/dependencies.d/base b/docker/production/s6-rc.d/frankenphp/dependencies.d/base new file mode 100644 index 00000000..e69de29b diff --git a/docker/production/s6-rc.d/frankenphp/run b/docker/production/s6-rc.d/frankenphp/run new file mode 100644 index 00000000..1193fcc3 --- /dev/null +++ b/docker/production/s6-rc.d/frankenphp/run @@ -0,0 +1,2 @@ +#!/command/with-contenv sh +frankenphp run --config /etc/frankenphp/Caddyfile --adapter caddyfile diff --git a/docker/production/s6-rc.d/frankenphp/type b/docker/production/s6-rc.d/frankenphp/type new file mode 100644 index 00000000..5883cff0 --- /dev/null +++ b/docker/production/s6-rc.d/frankenphp/type @@ -0,0 +1 @@ +longrun diff --git a/docker/production/s6-rc.d/supercronic/crontab b/docker/production/s6-rc.d/supercronic/crontab new file mode 100644 index 00000000..8382f92f --- /dev/null +++ b/docker/production/s6-rc.d/supercronic/crontab @@ -0,0 +1 @@ +* * * * * /usr/local/bin/php /var/www/html/spark tasks:run >> /dev/null 2>&1 diff --git a/docker/production/s6-rc.d/supercronic/dependencies.d/frankenphp b/docker/production/s6-rc.d/supercronic/dependencies.d/frankenphp new file mode 100644 index 00000000..e69de29b diff --git a/docker/production/s6-rc.d/supercronic/run b/docker/production/s6-rc.d/supercronic/run new file mode 100644 index 00000000..73b9b0cf --- /dev/null +++ b/docker/production/s6-rc.d/supercronic/run @@ -0,0 +1,2 @@ +#!/command/with-contenv sh +supercronic /etc/s6-overlay/s6-rc.d/supercronic/crontab diff --git a/docker/production/s6-rc.d/supercronic/type b/docker/production/s6-rc.d/supercronic/type new file mode 100644 index 00000000..5883cff0 --- /dev/null +++ b/docker/production/s6-rc.d/supercronic/type @@ -0,0 +1 @@ +longrun diff --git a/docker/production/s6-rc.d/user/contents.d/bootstrap b/docker/production/s6-rc.d/user/contents.d/bootstrap new file mode 100644 index 00000000..e69de29b diff --git a/docker/production/s6-rc.d/user/contents.d/frankenphp b/docker/production/s6-rc.d/user/contents.d/frankenphp new file mode 100644 index 00000000..e69de29b diff --git a/docker/production/s6-rc.d/user/contents.d/supercronic b/docker/production/s6-rc.d/user/contents.d/supercronic new file mode 100644 index 00000000..e69de29b diff --git a/docker/production/web-server/Dockerfile b/docker/production/web-server/Dockerfile deleted file mode 100644 index 3b2320a2..00000000 --- a/docker/production/web-server/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM docker.io/nginx:1.21-alpine - -VOLUME /var/www/html/media - -EXPOSE 80 - -WORKDIR /var/www/html - -COPY docker/production/web-server/entrypoint.sh /entrypoint.sh - -RUN chmod +x /entrypoint.sh && \ - apk add --no-cache curl - -HEALTHCHECK --interval=30s --timeout=3s CMD curl --fail http://localhost || exit 1 - -COPY docker/production/web-server/nginx.conf /etc/nginx/nginx.conf - -COPY castopod/public /var/www/html - -CMD ["/entrypoint.sh"] diff --git a/docker/production/web-server/entrypoint.sh b/docker/production/web-server/entrypoint.sh deleted file mode 100644 index 4365e2cd..00000000 --- a/docker/production/web-server/entrypoint.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -if [ -z "${CP_APP_HOSTNAME}" ] -then - echo "CP_APP_HOSTNAME is empty, using default" - CP_APP_HOSTNAME="app" -fi - -sed -i "s/CP_APP_HOSTNAME/${CP_APP_HOSTNAME}/" /etc/nginx/nginx.conf -nginx -g "daemon off;" diff --git a/docker/production/web-server/nginx.conf b/docker/production/web-server/nginx.conf deleted file mode 100644 index 425dcf24..00000000 --- a/docker/production/web-server/nginx.conf +++ /dev/null @@ -1,76 +0,0 @@ -worker_processes auto; - -error_log /var/log/nginx/error.log warn; -pid /var/run/nginx.pid; - -events { - worker_connections 1024; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main; - - sendfile on; - - keepalive_timeout 65; - - set_real_ip_from 10.0.0.0/8; - set_real_ip_from 172.16.0.0/12; - set_real_ip_from 192.168.0.0/16; - real_ip_header X-Real-IP; - - upstream php-handler { - server CP_APP_HOSTNAME:9000; - } - - server { - listen 80; - - root /var/www/html; - - index index.php index.html index.htm; - - client_max_body_size 1G; - fastcgi_buffers 64 4K; - - gzip on; - gzip_vary on; - gzip_comp_level 4; - gzip_min_length 256; - gzip_types application/atom+xml application/javascript audio/mpeg application/rss+xml image/bmp image/png image/jpeg image/webp image/svg+xml image/x-icon video/mp4 text/css text/plain text/html; - - location ~ /.*\.(png|ico|txt|js|js\.map)$ { - try_files $uri =404; - } - - location ~ /(assets|media)/.*$ { - try_files $uri =404; - } - - location /.well-known/GDPR.yml { - try_files $uri =404; - } - - location / { - fastcgi_param SCRIPT_FILENAME /opt/castopod/public/index.php; - include fastcgi_params; - fastcgi_index index.php; - fastcgi_pass php-handler; - } - - location ~ \.php$ { - try_files $uri =404; - fastcgi_param SCRIPT_FILENAME /opt/castopod/public/$fastcgi_script_name; - include fastcgi_params; - fastcgi_index index.php; - fastcgi_pass php-handler; - } - } -} diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..6240da8b --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,21 @@ +# build output +dist/ +# generated types +.astro/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + + +# environment variables +.env +.env.production + +# macOS-specific files +.DS_Store diff --git a/docs/.gitlab-ci.yml b/docs/.gitlab-ci.yml index d7f739a2..d0f1bde5 100644 --- a/docs/.gitlab-ci.yml +++ b/docs/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: node:16 +image: node:22 stages: - build @@ -6,26 +6,32 @@ stages: .documentation-setup: before_script: + - npm install --global corepack@latest + - corepack enable + - corepack prepare pnpm@latest-10 --activate + - pnpm config set store-dir .pnpm-store - cd docs - chmod +x ./scripts/i18n-filter.sh - - ./scripts/i18n-filter.sh src - - npm ci + - ./scripts/i18n-filter.sh src/content/docs + - pnpm install cache: + key: + files: + - docs/pnpm-lock.yaml paths: - - docs/node_modules/ + - .pnpm-store # This job only serves for validating that the docs builds correctly on all non deployment branches build: extends: .documentation-setup stage: build script: - - npm run build - except: - - develop - - main - - beta - - alpha + - pnpm run build + rules: + - if: $CI_COMMIT_BRANCH =~ /^(develop|main|alpha|beta|next)$/ + when: never + - when: on_success build-production: extends: .documentation-setup @@ -34,16 +40,13 @@ build-production: name: production url: https://docs.castopod.org/ script: - - npm run build + - BASE=/$CI_COMMIT_REF_SLUG pnpm run build --outDir=./dist/$CI_COMMIT_REF_SLUG --base=/$CI_COMMIT_REF_SLUG artifacts: paths: - - docs/.vitepress/dist + - docs/dist/$CI_COMMIT_REF_SLUG expire_in: 30 mins - only: - - develop - - main - - beta - - alpha + rules: + - if: $CI_COMMIT_BRANCH =~ /^(develop|main|alpha|beta|next)$/ deploy: stage: deploy @@ -53,15 +56,13 @@ deploy: variables: HOST: $DOCS_HOST USER: $DOCS_USER - TEMP_DIRECTORY: $DOCS_TEMP_DIRECTORY - DIRECTORY: $DOCS_DIRECTORY + TEMP_DIRECTORY: $DOCS_TEMP_DIRECTORY/$CI_COMMIT_REF_SLUG/ + DIRECTORY: $DOCS_DIRECTORY/$CI_COMMIT_REF_SLUG/ SSH_PORT: 3242 - SOURCE_FOLDER: "docs/.vitepress/dist/" + SOURCE_FOLDER: "docs/dist/$CI_COMMIT_REF_SLUG/" before_script: # install rsync for file transfers - apt-get update && apt-get install rsync -y - # ssh config - - "which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )" # Run ssh-agent (inside the build environment) - eval $(ssh-agent -s) # Add the SSH key stored in SSH_PRIVATE_KEY variable to the agent store @@ -71,8 +72,5 @@ deploy: script: - rsync -avzuh -e "ssh -p $SSH_PORT" $SOURCE_FOLDER $USER@$HOST:$TEMP_DIRECTORY --progress - ssh $USER@$HOST -p $SSH_PORT "rsync -rtv $TEMP_DIRECTORY $DIRECTORY" - only: - - develop - - main - - beta - - alpha + rules: + - if: $CI_COMMIT_BRANCH =~ /^(develop|main|alpha|beta|next)$/ diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts deleted file mode 100644 index c76d8335..00000000 --- a/docs/.vitepress/config.ts +++ /dev/null @@ -1,276 +0,0 @@ -import { defineConfig } from "vitepress"; - -export default defineConfig({ - srcDir: "src", - - head: [ - ["link", { rel: "icon", type: "image/x-icon", href: "/favicon.ico" }], - ["link", { rel: "canonical", href: "https://docs.castopod.org/" }], - ["meta", { name: "robots", content: "index, follow" }], - ["meta", { property: "og:type", content: "website" }], - [ - "meta", - { - property: "og:image", - content: "https://docs.castopod.org/images/open-graph.jpg", - }, - ], - ["meta", { property: "og:image:type", content: "image/jpeg" }], - ["meta", { property: "og:image:width", content: "1200" }], - ["meta", { property: "og:image:height", content: "630" }], - [ - "meta", - { - property: "og:image:alt", - content: - "Castopod mascot waving hello and holding a browser showcasing the Castopod documentation.", - }, - ], - ["meta", { property: "og:url", content: "https://docs.castopod.org/" }], - ["meta", { name: "twitter:site", content: "@castopod" }], - ["meta", { name: "twitter:card", content: "summary_large_image" }], - ["meta", { name: "twitter:creator", content: "@ad_aures" }], - [ - "script", - { - defer: "defer", - "data-domain": "docs.castopod.org", - src: "https://analytics.castopod.org/js/plausible.js", - }, - ], - ], - - locales: { - "/": { - lang: "en", - title: "Castopod documentation", - description: - "Check out the Castopod documentation! Install your own free & open-source podcast host, help make it better by contributing, or simply learn more about Castopod!", - }, - "/fr/": { - lang: "fr", - title: "Documentation Castopod", - description: - "Castopod est une plateforme d’hébergement gratuite & open-source conçue pour les podcasteurs qui veulent échanger et interagir avec leur public.", - }, - "/pt-BR/": { - lang: "pt-BR", - title: "Documentação Castopod", - description: - "Castopod é uma plataforma de hospedagem de código livre & aberto feita para podcasters que querem se envolver e interagir com seu público.", - }, - "/nn-NO/": { - lang: "nn-NO", - title: "Castopod dokumentasjon", - description: - "Castopod er ei open og gratis løysing for dei som vil køyra si eiga podkasting-plattform, og for podkastarar som vil engasjera og samhandla med publikum.", - }, - }, - - themeConfig: { - logo: "/images/castopod-icon.svg", - lastUpdated: "Last Updated", - repo: "https://code.castopod.org/adaures/castopod", - docsDir: "docs/src", - docsBranch: "develop", - editLinks: true, - locales: { - "/": { - label: "English", - selectText: "Languages", - repoLabel: "Source code", - nav: [ - { - text: "Home", - link: "https://castopod.org/", - }, - { - text: "Blog", - link: "https://blog.castopod.org/", - }, - { - text: "Github", - link: "https://github.com/ad-aures/castopod", - }, - ], - sidebar: { - "/": getGuideSidebarEn(), - }, - }, - "/fr/": { - label: "Français", - selectText: "Langues", - repoLabel: "Code source", - nav: [ - { - text: "Accueil", - link: "https://castopod.org/", - }, - { - text: "Blog", - link: "https://blog.castopod.org/", - }, - { - text: "Github", - link: "https://github.com/ad-aures/castopod", - }, - ], - sidebar: { - "/": getGuideSidebarFr(), - }, - }, - "/pt-BR/": { - label: "Português do Brasil", - selectText: "Línguas", - repoLabel: "Código fonte", - nav: [ - { - text: "Início", - link: "https://castopod.org/", - }, - { - text: "Blogue", - link: "https://blog.castopod.org/", - }, - { - text: "Github", - link: "https://github.com/ad-aures/castopod", - }, - ], - sidebar: { "/pt-BR/": getGuideSidebarPtBR() }, - }, - "/nn-NO/": { - label: "Norsk nynorsk", - selectText: "Språk", - repoLabel: "Kildekode", - nav: [ - { - text: "Heim", - link: "https://castopod.org/", - }, - { - text: "Blogg", - link: "https://blog.castopod.org/", - }, - { - text: "Github", - link: "https://github.com/ad-aures/castopod", - }, - ], - sidebar: { "/nn-NO/": getGuideSidebarNnNO() }, - }, - }, - }, -}); - -function getGuideSidebarEn() { - return [ - { - text: "Introduction", - link: "/", - }, - { - text: "Getting started", - children: [ - { text: "Install", link: "/getting-started/install" }, - { - text: "Docker", - link: "/getting-started/docker", - }, - { text: "Security", link: "/getting-started/security" }, - { text: "Update", link: "/getting-started/update" }, - ], - }, - { - text: "Contributing", - children: [ - { text: "Guide", link: "/contributing/guidelines" }, - { text: "Dev Setup", link: "/contributing/setup-development" }, - ], - }, - ]; -} - -function getGuideSidebarFr() { - return [ - { - text: "Introduction", - link: "/fr/", - }, - { - text: "Commencer", - children: [ - { text: "Installer", link: "/fr/getting-started/install" }, - { - text: "Docker", - link: "/fr/getting-started/docker", - }, - { text: "Sécurité", link: "/fr/getting-started/security" }, - { text: "Mise à jour", link: "/fr/getting-started/update" }, - ], - }, - { - text: "Contributing", - children: [ - { text: "Guide", link: "/fr/contributing/guidelines" }, - { text: "Dev Setup", link: "/fr/contributing/setup-development" }, - ], - }, - ]; -} - -function getGuideSidebarPtBR() { - return [ - { - text: "Introdução", - link: "/pt-BR/", - }, - { - text: "Começando", - children: [ - { text: "Instalar", link: "/pt-BR/getting-started/install" }, - { - text: "Docker", - link: "/pt-BR/getting-started/docker", - }, - { text: "Segurança", link: "/pt-BR/getting-started/security" }, - { text: "Atualizar", link: "/pt-BR/getting-started/update" }, - ], - }, - { - text: "Contributing", - children: [ - { text: "Guide", link: "/pt-BR/contributing/guidelines" }, - { text: "Dev Setup", link: "/pt-BR/contributing/setup-development" }, - ], - }, - ]; -} - -function getGuideSidebarNnNO() { - return [ - { - text: "Introduksjon", - link: "/nn-NO/", - }, - { - text: "Starter", - children: [ - { text: "Installer", link: "/nn-NO/getting-started/install" }, - { - text: "Docker", - link: "/nn-NO/getting-started/docker", - }, - { text: "Sikkerhet", link: "/nn-NO/getting-started/security" }, - { text: "Oppdaterer", link: "/nn-NO/getting-started/update" }, - ], - }, - { - text: "Contributing", - children: [ - { text: "Guide", link: "/nn-NO/contributing/guidelines" }, - { text: "Dev Setup", link: "/nn-NO/contributing/setup-development" }, - ], - }, - ]; -} diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts deleted file mode 100644 index a60960f7..00000000 --- a/docs/.vitepress/theme/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Theme from "vitepress/theme"; -import "./tailwind.css"; - -export default { - ...Theme, -}; diff --git a/docs/.vitepress/theme/tailwind.css b/docs/.vitepress/theme/tailwind.css deleted file mode 100644 index 7c05d4dd..00000000 --- a/docs/.vitepress/theme/tailwind.css +++ /dev/null @@ -1,2 +0,0 @@ -@tailwind components; -@tailwind utilities; diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..34351f25 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,54 @@ +# Castopod Docs + +[![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build) + +```sh +pnpm create astro@latest -- --template starlight +``` + +> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! + +## 🚀 Project Structure + +Inside of your Astro + Starlight project, you'll see the following folders and +files: + +```sh +. +├── public/ +├── src/ +│ ├── assets/ +│ ├── content/ +│ │ └── docs/ +│ └── content.config.ts +├── astro.config.mjs +├── package.json +└── tsconfig.json +``` + +Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. +Each file is exposed as a route based on its file name. + +Images can be added to `src/assets/` and embedded in Markdown with a relative +link. + +Static assets, like favicons, can be placed in the `public/` directory. + +## 🧞 Commands + +All commands are run from the root of the project, from a terminal: + +| Command | Action | +| :--------------------- | :----------------------------------------------- | +| `pnpm install` | Installs dependencies | +| `pnpm dev` | Starts local dev server at `localhost:4321` | +| `pnpm build` | Build your production site to `./dist/` | +| `pnpm preview` | Preview your build locally, before deploying | +| `pnpm astro ...` | Run CLI commands like `astro add`, `astro check` | +| `pnpm astro -- --help` | Get help using the Astro CLI | + +## 👀 Want to learn more? + +Check out [Starlight’s docs](https://starlight.astro.build/), read +[the Astro documentation](https://docs.astro.build), or jump into the +[Astro Discord server](https://astro.build/chat). diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs new file mode 100644 index 00000000..d641ec7f --- /dev/null +++ b/docs/astro.config.mjs @@ -0,0 +1,413 @@ +// @ts-check +import { defineConfig } from "astro/config"; +import starlight from "@astrojs/starlight"; +import starlightOpenAPI from "starlight-openapi"; + +const site = "https://docs.castopod.org/"; +const base = process.env.BASE ?? "/docs"; + +// https://astro.build/config +export default defineConfig({ + server: { + host: true, + }, + site, + base, + integrations: [ + starlight({ + title: "Castopod Docs", + description: + "Check out the Castopod documentation! Install your own free & open-source podcast host, help make it better by contributing, or simply learn more about Castopod!", + components: { + ThemeSelect: "./src/components/ThemeSelect.astro", + }, + logo: { + src: "./src/assets/castopod-logo-inline.svg", + replacesTitle: true, + }, + favicon: "/favicon.ico", + customCss: [ + "@fontsource/inter/400.css", + "@fontsource/inter/600.css", + "@fontsource/rubik/700.css", + "./src/styles/custom.css", + ], + head: [ + { + tag: "meta", + attrs: { + property: "og:type", + content: "website", + }, + }, + { + tag: "meta", + attrs: { + property: "og:image", + content: base + "/open-graph.jpg?v=1", + }, + }, + { + tag: "meta", + attrs: { property: "og:image:type", content: "image/jpeg" }, + }, + { tag: "meta", attrs: { property: "og:image:width", content: "1200" } }, + { tag: "meta", attrs: { property: "og:image:height", content: "630" } }, + { + tag: "meta", + attrs: { + property: "og:image:alt", + content: + "Castopod mascot waving hello and holding a browser showcasing the Castopod documentation.", + }, + }, + { + tag: "meta", + attrs: { property: "og:url", content: "https://docs.castopod.org/" }, + }, + { tag: "meta", attrs: { name: "twitter:site", content: "@castopod" } }, + { + tag: "meta", + attrs: { name: "twitter:card", content: "summary_large_image" }, + }, + { + tag: "meta", + attrs: { name: "twitter:creator", content: "@ad_aures" }, + }, + { + tag: "script", + attrs: { + src: "https://analytics.castopod.org/js/plausible.js", + "data-domain": "docs.castopod.org", + defer: true, + }, + }, + ], + social: [ + { + icon: "discord", + label: "Discord", + href: "https://castopod.org/chat", + }, + { + icon: "blueSky", + label: "Bluesky", + href: "https://bsky.app/profile/castopod.org", + }, + { + icon: "mastodon", + label: "Mastodon", + href: "https://podlibre.social/@Castopod", + }, + { + icon: "gitlab", + label: "Source code", + href: "https://code.castopod.org/adaures/castopod", + }, + { + icon: "github", + label: "Github", + href: "https://github.com/ad-aures/castopod", + }, + ], + defaultLocale: "en", + locales: { + en: { + label: "English", + }, + ca: { + label: "Català", + }, + de: { + label: "Deutsch", + }, + es: { + label: "Español", + }, + fr: { + label: "Français", + }, + "nn-no": { + label: "Norsk nynorsk", + lang: "nn-NO", + }, + "pt-br": { + label: "Português do Brasil", + lang: "pt-BR", + }, + "sr-latn": { + label: "Srpski", + lang: "sr-Latn", + }, + "zh-hans": { + label: "中文", + lang: "zh-Hans", + }, + }, + plugins: [ + // Generate the OpenAPI documentation pages. + starlightOpenAPI([ + { + base: "en/api", + label: "API reference", + schema: "../modules/Api/Rest/V1/schema.yaml", + collapsed: true, + }, + ]), + ], + sidebar: [ + { + label: "Introduction", + link: "/", + translations: { + fr: "Installer", + "pt-br": "Instalar", + "nn-no": "Installer", + }, + }, + { + label: "Getting started", + translations: { + fr: "Commencer", + "pt-br": "Começando", + "nn-no": "Starter", + }, + items: [ + // Each item here is one entry in the navigation menu. + { + label: "Install", + link: "/getting-started/install/", + translations: { + fr: "Installer", + "pt-br": "Instalar", + "nn-no": "Installer", + }, + }, + { + label: "Docker", + link: "/getting-started/docker/", + }, + { + label: "Security", + link: "/getting-started/security/", + translations: { + fr: "Sécurité", + "pt-br": "Segurança", + "nn-no": "Sikkerhet", + }, + }, + { + label: "Update", + link: "/getting-started/update/", + translations: { + fr: "Mise à jour", + "pt-br": "Atualizar", + "nn-no": "Oppdaterer", + }, + }, + { + label: "Auth", + link: "/getting-started/auth/", + translations: { + fr: "Authentification", + "pt-br": "Autenticação", + "nn-no": "Autentisering", + }, + }, + { + label: "Create your first podcast", + link: "/getting-started/create-podcast/", + translations: {}, + }, + { + label: "Create your first episode", + link: "/getting-started/create-episode/", + translations: {}, + }, + ], + }, + { + label: "Plugins", + items: [ + { + label: "Introduction", + link: "/plugins/", + }, + { + label: "Install plugins", + link: "/plugins/install", + }, + { + label: "Create a plugin", + link: "/plugins/create", + }, + { + label: "Share your plugin", + link: "/plugins/share", + }, + { + label: "Reference", + items: [ + { + label: "plugins.json", + link: "/plugins/reference/plugins-json", + }, + { + label: "plugins-lock.json", + link: "/plugins/reference/plugins-lock-json", + }, + { + label: "manifest.json", + link: "/plugins/reference/manifest", + }, + { + label: "hooks", + link: "/plugins/reference/hooks", + }, + ], + }, + ], + }, + // TODO: openapi plugin does not handle i18n, manual sidebar workaround + // Add the generated sidebar group to the sidebar. + // ...openAPISidebarGroups, + { + label: "API reference", + translations: {}, + items: [ + { + label: "Overview", + link: "/api", + }, + { + label: "Operations", + items: [ + { + label: "Get all podcasts", + link: "/api/operations/get-all-podcasts", + }, + { + label: "Get podcast by ID", + link: "/api/operations/get-podcast-by-id", + }, + { + label: "Get all episodes", + link: "/api/operations/get-all-episodes", + }, + { + label: "Add a new episode", + link: "/api/operations/add-episode", + }, + { + label: "Get episode by ID", + link: "/api/operations/get-episode-by-id", + }, + { + label: "Publish an episode", + link: "/api/operations/publish-episode", + }, + ], + }, + ], + }, + { + label: "User guide", + translations: {}, + items: [ + { + label: "Introduction", + link: "/user-guide/", + }, + { + label: "Manage your instance", + translations: {}, + collapsed: true, + items: [ + { + label: "Introduction", + link: "/user-guide/instance/", + }, + { + label: "Add a podcast", + link: "/user-guide/instance/podcast", + translations: {}, + }, + { + label: "Persons", + link: "/user-guide/instance/persons", + translations: {}, + }, + + { + label: "Fediverse", + link: "/user-guide/instance/fediverse", + translations: {}, + }, + { + label: "Users", + link: "/user-guide/instance/users", + translations: {}, + }, + { + label: "Pages", + link: "/user-guide/instance/pages", + translations: {}, + }, + { + label: "Settings", + link: "/user-guide/instance/settings", + translations: {}, + }, + ], + }, + { + label: "Manage your podcasts", + translations: {}, + collapsed: true, + items: [ + { + label: "Introduction", + link: "/user-guide/podcast/", + }, + { + label: "Podcast dashboard", + link: "/user-guide/podcast/dashboard", + translations: {}, + }, + { + label: "Episodes", + link: "/user-guide/podcast/episodes", + translations: {}, + }, + + { + label: "Analytics", + link: "/user-guide/podcast/analytics", + translations: {}, + }, + { + label: "Broadcasting", + link: "/user-guide/podcast/broadcast", + translations: {}, + }, + { + label: "Contributors", + link: "/user-guide/podcast/contributors", + translations: {}, + }, + ], + }, + { + label: "Website overview", + link: "/user-guide/website/", + translations: {}, + }, + ], + }, + ], + editLink: { + baseUrl: "https://code.castopod.org/adaures/castopod/-/edit/main/docs/", + }, + }), + ], +}); diff --git a/docs/package-lock.json b/docs/package-lock.json deleted file mode 100644 index 82e5454b..00000000 --- a/docs/package-lock.json +++ /dev/null @@ -1,3577 +0,0 @@ -{ - "name": "castopod-docs", - "version": "0.0.0-development", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "castopod-docs", - "version": "0.0.0-development", - "license": "AGPL-3.0-or-later", - "devDependencies": { - "tailwindcss": "^3.0.23", - "vitepress": "^0.22.3" - } - }, - "node_modules/@algolia/autocomplete-core": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.5.2.tgz", - "integrity": "sha512-DY0bhyczFSS1b/CqJlTE/nQRtnTAHl6IemIkBy0nEWnhDzRDdtdx4p5Uuk3vwAFxwEEgi1WqKwgSSMx6DpNL4A==", - "dev": true, - "dependencies": { - "@algolia/autocomplete-shared": "1.5.2" - } - }, - "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.5.2.tgz", - "integrity": "sha512-3MRYnYQFJyovANzSX2CToS6/5cfVjbLLqFsZTKcvF3abhQzxbqwwaMBlJtt620uBUOeMzhdfasKhCc40+RHiZw==", - "dev": true, - "dependencies": { - "@algolia/autocomplete-shared": "1.5.2" - }, - "peerDependencies": { - "@algolia/client-search": "^4.9.1", - "algoliasearch": "^4.9.1" - } - }, - "node_modules/@algolia/autocomplete-shared": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.5.2.tgz", - "integrity": "sha512-ylQAYv5H0YKMfHgVWX0j0NmL8XBcAeeeVQUmppnnMtzDbDnca6CzhKj3Q8eF9cHCgcdTDdb5K+3aKyGWA0obug==", - "dev": true - }, - "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.12.2.tgz", - "integrity": "sha512-z8LjFsQc0B6h6LEE3pkUGM4ErVktn6bkFbhnYbTccjmFVQ+wXFJd/D63e0WtaC+hwRB1xq8uKhkz9oojEKEsGA==", - "dev": true, - "dependencies": { - "@algolia/cache-common": "4.12.2" - } - }, - "node_modules/@algolia/cache-common": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.12.2.tgz", - "integrity": "sha512-r//r7MF0Na0HxD2BHnjWsDKuI72Z5UEf/Rb/8MC08XKBsjCwBihGxWxycjRcNGjNEIxJBsvRMIEOipcd9qD54g==", - "dev": true - }, - "node_modules/@algolia/cache-in-memory": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.12.2.tgz", - "integrity": "sha512-opWpbBUloP1fcTG3wBDnAfcoyNXW5GFDgGtLXrSANdfnelPKkr3O8j01ZTkRlPIuBDR0izGZG8MVWMDlTf71Bw==", - "dev": true, - "dependencies": { - "@algolia/cache-common": "4.12.2" - } - }, - "node_modules/@algolia/client-account": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.12.2.tgz", - "integrity": "sha512-HZqEyeVVjzOlfoSUyc+7+ueEJmRgqSuC+hqQOGECYa5JVno4d8eRVuDAMOb87I2LOdg/WoFMcAtaaRq2gpfV/w==", - "dev": true, - "dependencies": { - "@algolia/client-common": "4.12.2", - "@algolia/client-search": "4.12.2", - "@algolia/transporter": "4.12.2" - } - }, - "node_modules/@algolia/client-analytics": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.12.2.tgz", - "integrity": "sha512-7ktimzesu+vk3l+eG9w/nQh6/9AoIieCKmoiRIguKh6okGsaSBrcTHvUwIQEIiliqPuAFBk2M8eXYFqOZzwCZw==", - "dev": true, - "dependencies": { - "@algolia/client-common": "4.12.2", - "@algolia/client-search": "4.12.2", - "@algolia/requester-common": "4.12.2", - "@algolia/transporter": "4.12.2" - } - }, - "node_modules/@algolia/client-common": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.12.2.tgz", - "integrity": "sha512-+dTicT1lklwOpeoiDspUoRSQYHhrr2IzllrX89/WuTPEBm2eww1xurqrSTQYC0MuVeX1s9/i4k34Q0ZnspypWg==", - "dev": true, - "dependencies": { - "@algolia/requester-common": "4.12.2", - "@algolia/transporter": "4.12.2" - } - }, - "node_modules/@algolia/client-personalization": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.12.2.tgz", - "integrity": "sha512-JBW3vYFGIm5sAAy3cLUdmUCpmSAdreo5S1fERg7xgF6KyxGrwyy5BViTNWrOKG+av2yusk1wKydOYJ1Fbpbaxw==", - "dev": true, - "dependencies": { - "@algolia/client-common": "4.12.2", - "@algolia/requester-common": "4.12.2", - "@algolia/transporter": "4.12.2" - } - }, - "node_modules/@algolia/client-search": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.12.2.tgz", - "integrity": "sha512-JIqi14TgfEqAooNbSPBC1ZCk3Pnviqlaz9KofAqWBxSRTpPUFnU/XQCU5ihR0PC68SFVDnU/Y9cak/XotXPUeg==", - "dev": true, - "dependencies": { - "@algolia/client-common": "4.12.2", - "@algolia/requester-common": "4.12.2", - "@algolia/transporter": "4.12.2" - } - }, - "node_modules/@algolia/logger-common": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.12.2.tgz", - "integrity": "sha512-iOiJAymLjq137G7+8EQuUEkrgta0cZGMg6scp8s4hJ+X6k+6By4nyptdkCWYwKLsW/Xy927QcIhGlkWV78vQIQ==", - "dev": true - }, - "node_modules/@algolia/logger-console": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.12.2.tgz", - "integrity": "sha512-veuQZyTSqHoHJtr9mLMnYeal9Mee6hCie4eqY+645VbeOrgT9p/kCMbKg5GLJGoLPlXGu7C0XpHyUj5k7/NQyw==", - "dev": true, - "dependencies": { - "@algolia/logger-common": "4.12.2" - } - }, - "node_modules/@algolia/requester-browser-xhr": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.12.2.tgz", - "integrity": "sha512-FpFdHNd81tS3zj6Glqd+lt+RV0ljPExKtx+QB+gani6HWZ9YlSCM+Zl82T4ibxN+hmkrMeAyT+TMzS0jiGhGyQ==", - "dev": true, - "dependencies": { - "@algolia/requester-common": "4.12.2" - } - }, - "node_modules/@algolia/requester-common": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.12.2.tgz", - "integrity": "sha512-4szj/lvDQf/u8EyyRBBRZD1ZkKDyLBbckLj7meQDlnbfwnW1UpLwpB2l3XJ9wDmDSftGxUCeTl5oMFe4z9OEvQ==", - "dev": true - }, - "node_modules/@algolia/requester-node-http": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.12.2.tgz", - "integrity": "sha512-UXfJNZt2KMwjBjiOa3cJ/PyoXWZa/F1vy6rdyG4xQeZDcLbqKP3O2b+bOJcGPmFbmdwBhtAyMVLt+hvAvAVfOw==", - "dev": true, - "dependencies": { - "@algolia/requester-common": "4.12.2" - } - }, - "node_modules/@algolia/transporter": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.12.2.tgz", - "integrity": "sha512-PUq79if4CukXsm27ymTQ3eD3juSvMcyJmt6mxCkSFE0zQRL4ert61HBlNH6S9y/quUVe3g7oggfHq3d5pdpqZA==", - "dev": true, - "dependencies": { - "@algolia/cache-common": "4.12.2", - "@algolia/logger-common": "4.12.2", - "@algolia/requester-common": "4.12.2" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", - "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@docsearch/css": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.0.0.tgz", - "integrity": "sha512-1kkV7tkAsiuEd0shunYRByKJe3xQDG2q7wYg24SOw1nV9/2lwEd4WrUYRJC/ukGTl2/kHeFxsaUvtiOy0y6fFA==", - "dev": true - }, - "node_modules/@docsearch/js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.0.0.tgz", - "integrity": "sha512-j3tUJWlgW3slYqzGB8fm7y05kh2qqrIK1dZOXHeMUm/5gdKE85fiz/ltfCPMDFb/MXF+bLZChJXSMzqY0Ck30Q==", - "dev": true, - "dependencies": { - "@docsearch/react": "3.0.0", - "preact": "^10.0.0" - } - }, - "node_modules/@docsearch/react": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.0.0.tgz", - "integrity": "sha512-yhMacqS6TVQYoBh/o603zszIb5Bl8MIXuOc6Vy617I74pirisDzzcNh0NEaYQt50fVVR3khUbeEhUEWEWipESg==", - "dev": true, - "dependencies": { - "@algolia/autocomplete-core": "1.5.2", - "@algolia/autocomplete-preset-algolia": "1.5.2", - "@docsearch/css": "3.0.0", - "algoliasearch": "^4.0.0" - }, - "peerDependencies": { - "@types/react": ">= 16.8.0 < 18.0.0", - "react": ">= 16.8.0 < 18.0.0", - "react-dom": ">= 16.8.0 < 18.0.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, - "node_modules/@types/prop-types": { - "version": "15.7.4", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", - "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", - "dev": true, - "peer": true - }, - "node_modules/@types/react": { - "version": "17.0.39", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.39.tgz", - "integrity": "sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==", - "dev": true, - "peer": true, - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "dev": true, - "peer": true - }, - "node_modules/@vitejs/plugin-vue": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-2.2.4.tgz", - "integrity": "sha512-ev9AOlp0ljCaDkFZF3JwC/pD2N4Hh+r5srl5JHM6BKg5+99jiiK0rE/XaRs3pVm1wzyKkjUy/StBSoXX5fFzcw==", - "dev": true, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "vite": "^2.5.10", - "vue": "^3.2.25" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.31.tgz", - "integrity": "sha512-aKno00qoA4o+V/kR6i/pE+aP+esng5siNAVQ422TkBNM6qA4veXiZbSe8OTXHXquEi/f6Akc+nLfB4JGfe4/WQ==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.16.4", - "@vue/shared": "3.2.31", - "estree-walker": "^2.0.2", - "source-map": "^0.6.1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.31.tgz", - "integrity": "sha512-60zIlFfzIDf3u91cqfqy9KhCKIJgPeqxgveH2L+87RcGU/alT6BRrk5JtUso0OibH3O7NXuNOQ0cDc9beT0wrg==", - "dev": true, - "dependencies": { - "@vue/compiler-core": "3.2.31", - "@vue/shared": "3.2.31" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.31.tgz", - "integrity": "sha512-748adc9msSPGzXgibHiO6T7RWgfnDcVQD+VVwYgSsyyY8Ans64tALHZANrKtOzvkwznV/F4H7OAod/jIlp/dkQ==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.31", - "@vue/compiler-dom": "3.2.31", - "@vue/compiler-ssr": "3.2.31", - "@vue/reactivity-transform": "3.2.31", - "@vue/shared": "3.2.31", - "estree-walker": "^2.0.2", - "magic-string": "^0.25.7", - "postcss": "^8.1.10", - "source-map": "^0.6.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.31.tgz", - "integrity": "sha512-mjN0rqig+A8TVDnsGPYJM5dpbjlXeHUm2oZHZwGyMYiGT/F4fhJf/cXy8QpjnLQK4Y9Et4GWzHn9PS8AHUnSkw==", - "dev": true, - "dependencies": { - "@vue/compiler-dom": "3.2.31", - "@vue/shared": "3.2.31" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.31.tgz", - "integrity": "sha512-HVr0l211gbhpEKYr2hYe7hRsV91uIVGFYNHj73njbARVGHQvIojkImKMaZNDdoDZOIkMsBc9a1sMqR+WZwfSCw==", - "dev": true, - "dependencies": { - "@vue/shared": "3.2.31" - } - }, - "node_modules/@vue/reactivity-transform": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.31.tgz", - "integrity": "sha512-uS4l4z/W7wXdI+Va5pgVxBJ345wyGFKvpPYtdSgvfJfX/x2Ymm6ophQlXXB6acqGHtXuBqNyyO3zVp9b1r0MOA==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.31", - "@vue/shared": "3.2.31", - "estree-walker": "^2.0.2", - "magic-string": "^0.25.7" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.31.tgz", - "integrity": "sha512-Kcog5XmSY7VHFEMuk4+Gap8gUssYMZ2+w+cmGI6OpZWYOEIcbE0TPzzPHi+8XTzAgx1w/ZxDFcXhZeXN5eKWsA==", - "dev": true, - "dependencies": { - "@vue/reactivity": "3.2.31", - "@vue/shared": "3.2.31" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.31.tgz", - "integrity": "sha512-N+o0sICVLScUjfLG7u9u5XCjvmsexAiPt17GNnaWHJUfsKed5e85/A3SWgKxzlxx2SW/Hw7RQxzxbXez9PtY3g==", - "dev": true, - "dependencies": { - "@vue/runtime-core": "3.2.31", - "@vue/shared": "3.2.31", - "csstype": "^2.6.8" - } - }, - "node_modules/@vue/runtime-dom/node_modules/csstype": { - "version": "2.6.20", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz", - "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==", - "dev": true - }, - "node_modules/@vue/server-renderer": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.31.tgz", - "integrity": "sha512-8CN3Zj2HyR2LQQBHZ61HexF5NReqngLT3oahyiVRfSSvak+oAvVmu8iNLSu6XR77Ili2AOpnAt1y8ywjjqtmkg==", - "dev": true, - "dependencies": { - "@vue/compiler-ssr": "3.2.31", - "@vue/shared": "3.2.31" - }, - "peerDependencies": { - "vue": "3.2.31" - } - }, - "node_modules/@vue/shared": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.31.tgz", - "integrity": "sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ==", - "dev": true - }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "dev": true, - "dependencies": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/algoliasearch": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.12.2.tgz", - "integrity": "sha512-bn1P9+V415zeDQJtXn+1SwuwedEAv9/LJAxt8XwR6ygH/sMwaHSm2hpkz8wIbCBt/tKQ43TL672Kyxzv5PwGgQ==", - "dev": true, - "dependencies": { - "@algolia/cache-browser-local-storage": "4.12.2", - "@algolia/cache-common": "4.12.2", - "@algolia/cache-in-memory": "4.12.2", - "@algolia/client-account": "4.12.2", - "@algolia/client-analytics": "4.12.2", - "@algolia/client-common": "4.12.2", - "@algolia/client-personalization": "4.12.2", - "@algolia/client-search": "4.12.2", - "@algolia/logger-common": "4.12.2", - "@algolia/logger-console": "4.12.2", - "@algolia/requester-browser-xhr": "4.12.2", - "@algolia/requester-common": "4.12.2", - "@algolia/requester-node-http": "4.12.2", - "@algolia/transporter": "4.12.2" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.1.tgz", - "integrity": "sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==", - "dev": true - }, - "node_modules/autoprefixer": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz", - "integrity": "sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==", - "dev": true, - "peer": true, - "dependencies": { - "browserslist": "^4.19.1", - "caniuse-lite": "^1.0.30001297", - "fraction.js": "^4.1.2", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.19.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.3.tgz", - "integrity": "sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg==", - "dev": true, - "peer": true, - "dependencies": { - "caniuse-lite": "^1.0.30001312", - "electron-to-chromium": "^1.4.71", - "escalade": "^3.1.1", - "node-releases": "^2.0.2", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001313", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001313.tgz", - "integrity": "sha512-rI1UN0koZUiKINjysQDuRi2VeSCce3bYJNmDcj3PIKREiAmjakugBul1QSkg/fPrlULYl6oWfGg3PbgOSY9X4Q==", - "dev": true, - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "dev": true, - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", - "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", - "dev": true, - "peer": true - }, - "node_modules/defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, - "node_modules/detective": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", - "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", - "dev": true, - "dependencies": { - "acorn-node": "^1.6.1", - "defined": "^1.0.0", - "minimist": "^1.1.1" - }, - "bin": { - "detective": "bin/detective.js" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.4.75", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.75.tgz", - "integrity": "sha512-LxgUNeu3BVU7sXaKjUDD9xivocQLxFtq6wgERrutdY/yIOps3ODOZExK1jg8DTEg4U8TUCb5MLGeWFOYuxjF3Q==", - "dev": true, - "peer": true - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/esbuild": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.25.tgz", - "integrity": "sha512-4JHEIOMNFvK09ziiL+iVmldIhLbn49V4NAVo888tcGFKedEZY/Y8YapfStJ6zSE23tzYPKxqKwQBnQoIO0BI/Q==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "esbuild-android-64": "0.14.25", - "esbuild-android-arm64": "0.14.25", - "esbuild-darwin-64": "0.14.25", - "esbuild-darwin-arm64": "0.14.25", - "esbuild-freebsd-64": "0.14.25", - "esbuild-freebsd-arm64": "0.14.25", - "esbuild-linux-32": "0.14.25", - "esbuild-linux-64": "0.14.25", - "esbuild-linux-arm": "0.14.25", - "esbuild-linux-arm64": "0.14.25", - "esbuild-linux-mips64le": "0.14.25", - "esbuild-linux-ppc64le": "0.14.25", - "esbuild-linux-riscv64": "0.14.25", - "esbuild-linux-s390x": "0.14.25", - "esbuild-netbsd-64": "0.14.25", - "esbuild-openbsd-64": "0.14.25", - "esbuild-sunos-64": "0.14.25", - "esbuild-windows-32": "0.14.25", - "esbuild-windows-64": "0.14.25", - "esbuild-windows-arm64": "0.14.25" - } - }, - "node_modules/esbuild-android-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.25.tgz", - "integrity": "sha512-L5vCUk7TzFbBnoESNoXjU3x9+/+7TDIE/1mTfy/erAfvZAqC+S3sp/Qa9wkypFMcFvN9FzvESkTlpeQDolREtQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-android-arm64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.25.tgz", - "integrity": "sha512-4jv5xPjM/qNm27T5j3ZEck0PvjgQtoMHnz4FzwF5zNP56PvY2CT0WStcAIl6jNlsuDdN63rk2HRBIsO6xFbcFw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.25.tgz", - "integrity": "sha512-TGp8tuudIxOyWd1+8aYPxQmC1ZQyvij/AfNBa35RubixD0zJ1vkKHVAzo0Zao1zcG6pNqiSyzfPto8vmg0s7oA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-arm64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.25.tgz", - "integrity": "sha512-oTcDgdm0MDVEmw2DWu8BV68pYuImpFgvWREPErBZmNA4MYKGuBRaCiJqq6jZmBR1x+3y1DWCjez+5uLtuAm6mw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.25.tgz", - "integrity": "sha512-ueAqbnMZ8arnuLH8tHwTCQYeptnHOUV7vA6px6j4zjjQwDx7TdP7kACPf3TLZLdJQ3CAD1XCvQ2sPhX+8tacvQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-arm64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.25.tgz", - "integrity": "sha512-+ZVWud2HKh+Ob6k/qiJWjBtUg4KmJGGmbvEXXW1SNKS7hW7HU+Zq2ZCcE1akFxOPkVB+EhOty/sSek30tkCYug==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-32": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.25.tgz", - "integrity": "sha512-3OP/lwV3kCzEz45tobH9nj+uE4ubhGsfx+tn0L26WAGtUbmmcRpqy7XRG/qK7h1mClZ+eguIANcQntYMdYklfw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.25.tgz", - "integrity": "sha512-+aKHdHZmX9qwVlQmu5xYXh7GsBFf4TWrePgeJTalhXHOG7NNuUwoHmketGiZEoNsWyyqwH9rE5BC+iwcLY30Ug==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.25.tgz", - "integrity": "sha512-aTLcE2VBoLydL943REcAcgnDi3bHtmULSXWLbjtBdtykRatJVSxKMjK9YlBXUZC4/YcNQfH7AxwVeQr9fNxPhw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.25.tgz", - "integrity": "sha512-UxfenPx/wSZx55gScCImPtXekvZQLI2GW3qe5dtlmU7luiqhp5GWPzGeQEbD3yN3xg/pHc671m5bma5Ns7lBHw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-mips64le": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.25.tgz", - "integrity": "sha512-wLWYyqVfYx9Ur6eU5RT92yJVsaBGi5RdkoWqRHOqcJ38Kn60QMlcghsKeWfe9jcYut8LangYZ98xO1LxIoSXrQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-ppc64le": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.25.tgz", - "integrity": "sha512-0dR6Csl6Zas3g4p9ULckEl8Mo8IInJh33VCJ3eaV1hj9+MHGdmDOakYMN8MZP9/5nl+NU/0ygpd14cWgy8uqRw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-riscv64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.25.tgz", - "integrity": "sha512-J4d20HDmTrgvhR0bdkDhvvJGaikH3LzXQnNaseo8rcw9Yqby9A90gKUmWpfwqLVNRILvNnAmKLfBjCKU9ajg8w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-s390x": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.25.tgz", - "integrity": "sha512-YI2d5V6nTE73ZnhEKQD7MtsPs1EtUZJ3obS21oxQxGbbRw1G+PtJKjNyur+3t6nzHP9oTg6GHQ3S3hOLLmbDIQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-netbsd-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.25.tgz", - "integrity": "sha512-TKIVgNWLUOkr+Exrye70XTEE1lJjdQXdM4tAXRzfHE9iBA7LXWcNtVIuSnphTqpanPzTDFarF0yqq4kpbC6miA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-openbsd-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.25.tgz", - "integrity": "sha512-QgFJ37A15D7NIXBTYEqz29+uw3nNBOIyog+3kFidANn6kjw0GHZ0lEYQn+cwjyzu94WobR+fes7cTl/ZYlHb1A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-sunos-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.25.tgz", - "integrity": "sha512-rmWfjUItYIVlqr5EnTH1+GCxXiBOC42WBZ3w++qh7n2cS9Xo0lO5pGSG2N+huOU2fX5L+6YUuJ78/vOYvefeFw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-32": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.25.tgz", - "integrity": "sha512-HGAxVUofl3iUIz9W10Y9XKtD0bNsK9fBXv1D55N/ljNvkrAYcGB8YCm0v7DjlwtyS6ws3dkdQyXadbxkbzaKOA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.25.tgz", - "integrity": "sha512-TirEohRkfWU9hXLgoDxzhMQD1g8I2mOqvdQF2RS9E/wbkORTAqJHyh7wqGRCQAwNzdNXdg3JAyhQ9/177AadWA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-arm64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.25.tgz", - "integrity": "sha512-4ype9ERiI45rSh+R8qUoBtaj6kJvUOI7oVLhKqPEpcF4Pa5PpT3hm/mXAyotJHREkHpM87PAJcA442mLnbtlNA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fraction.js": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.3.tgz", - "integrity": "sha512-pUHWWt6vHzZZiQJcM6S/0PXfS+g6FM4BF5rj9wZyreivhQPdsh5PpE25VtSNxq80wHS5RfY51Ii+8Z0Zl/pmzg==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://www.patreon.com/infusion" - } - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/lilconfig": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", - "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "peer": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", - "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", - "dev": true, - "peer": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.4.7", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.7.tgz", - "integrity": "sha512-L9Ye3r6hkkCeOETQX6iOaWZgjp3LL6Lpqm6EtgbKrgqGGteRMNb9vzBfRL96YOSu8o7x3MfIH9Mo5cPJFGrW6A==", - "dev": true, - "dependencies": { - "nanoid": "^3.3.1", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/postcss-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz", - "integrity": "sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==", - "dev": true, - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.3.3" - } - }, - "node_modules/postcss-load-config": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.3.tgz", - "integrity": "sha512-5EYgaM9auHGtO//ljHH+v/aC/TQ5LHXtL7bQajNAUBKUVKiYE8rYpFms7+V26D9FncaGe2zwCoPQsFKb5zF/Hw==", - "dev": true, - "dependencies": { - "lilconfig": "^2.0.4", - "yaml": "^1.10.2" - }, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-nested": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz", - "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.6" - }, - "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz", - "integrity": "sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "node_modules/preact": { - "version": "10.6.6", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.6.6.tgz", - "integrity": "sha512-dgxpTFV2vs4vizwKohYKkk7g7rmp1wOOcfd4Tz3IB3Wi+ivZzsn/SpeKJhRENSE+n8sUfsAl4S3HiCVT923ABw==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/prismjs": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", - "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "dev": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "dev": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - }, - "peerDependencies": { - "react": "17.0.2" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "2.69.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.69.1.tgz", - "integrity": "sha512-xaQKTomUVZBopk38EIshM/kOoPFkKWisgBV7Emy80coP9MOSLUDrba1jKZhqH0iS5DoGcRbbcuyl/BzblV8w5w==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "dev": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tailwindcss": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.0.23.tgz", - "integrity": "sha512-+OZOV9ubyQ6oI2BXEhzw4HrqvgcARY38xv3zKcjnWtMIZstEsXdI9xftd1iB7+RbOnj2HOEzkA0OyB5BaSxPQA==", - "dev": true, - "dependencies": { - "arg": "^5.0.1", - "chalk": "^4.1.2", - "chokidar": "^3.5.3", - "color-name": "^1.1.4", - "cosmiconfig": "^7.0.1", - "detective": "^5.2.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.2.11", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "normalize-path": "^3.0.0", - "object-hash": "^2.2.0", - "postcss": "^8.4.6", - "postcss-js": "^4.0.0", - "postcss-load-config": "^3.1.0", - "postcss-nested": "5.0.6", - "postcss-selector-parser": "^6.0.9", - "postcss-value-parser": "^4.2.0", - "quick-lru": "^5.1.1", - "resolve": "^1.22.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=12.13.0" - }, - "peerDependencies": { - "autoprefixer": "^10.0.2", - "postcss": "^8.0.9" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "node_modules/vite": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-2.8.6.tgz", - "integrity": "sha512-e4H0QpludOVKkmOsRyqQ7LTcMUDF3mcgyNU4lmi0B5JUbe0ZxeBBl8VoZ8Y6Rfn9eFKYtdXNPcYK97ZwH+K2ug==", - "dev": true, - "dependencies": { - "esbuild": "^0.14.14", - "postcss": "^8.4.6", - "resolve": "^1.22.0", - "rollup": "^2.59.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": ">=12.2.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "less": "*", - "sass": "*", - "stylus": "*" - }, - "peerDependenciesMeta": { - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - } - } - }, - "node_modules/vitepress": { - "version": "0.22.3", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-0.22.3.tgz", - "integrity": "sha512-Yfvu/rent2vp/TXIDZMutS6ft2TJPn4xngS48PYFWDEbuFI2ccUAXM481lF1qVVnCKxfh4g8e/KPvevSJdg1Bw==", - "dev": true, - "dependencies": { - "@docsearch/css": "^3.0.0-alpha.41", - "@docsearch/js": "^3.0.0-alpha.41", - "@vitejs/plugin-vue": "^2.2.0", - "prismjs": "^1.25.0", - "vite": "^2.8.1", - "vue": "^3.2.31" - }, - "bin": { - "vitepress": "bin/vitepress.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/vue": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.31.tgz", - "integrity": "sha512-odT3W2tcffTiQCy57nOT93INw1auq5lYLLYtWpPYQQYQOOdHiqFct9Xhna6GJ+pJQaF67yZABraH47oywkJgFw==", - "dev": true, - "dependencies": { - "@vue/compiler-dom": "3.2.31", - "@vue/compiler-sfc": "3.2.31", - "@vue/runtime-dom": "3.2.31", - "@vue/server-renderer": "3.2.31", - "@vue/shared": "3.2.31" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - } - }, - "dependencies": { - "@algolia/autocomplete-core": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.5.2.tgz", - "integrity": "sha512-DY0bhyczFSS1b/CqJlTE/nQRtnTAHl6IemIkBy0nEWnhDzRDdtdx4p5Uuk3vwAFxwEEgi1WqKwgSSMx6DpNL4A==", - "dev": true, - "requires": { - "@algolia/autocomplete-shared": "1.5.2" - } - }, - "@algolia/autocomplete-preset-algolia": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.5.2.tgz", - "integrity": "sha512-3MRYnYQFJyovANzSX2CToS6/5cfVjbLLqFsZTKcvF3abhQzxbqwwaMBlJtt620uBUOeMzhdfasKhCc40+RHiZw==", - "dev": true, - "requires": { - "@algolia/autocomplete-shared": "1.5.2" - } - }, - "@algolia/autocomplete-shared": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.5.2.tgz", - "integrity": "sha512-ylQAYv5H0YKMfHgVWX0j0NmL8XBcAeeeVQUmppnnMtzDbDnca6CzhKj3Q8eF9cHCgcdTDdb5K+3aKyGWA0obug==", - "dev": true - }, - "@algolia/cache-browser-local-storage": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.12.2.tgz", - "integrity": "sha512-z8LjFsQc0B6h6LEE3pkUGM4ErVktn6bkFbhnYbTccjmFVQ+wXFJd/D63e0WtaC+hwRB1xq8uKhkz9oojEKEsGA==", - "dev": true, - "requires": { - "@algolia/cache-common": "4.12.2" - } - }, - "@algolia/cache-common": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.12.2.tgz", - "integrity": "sha512-r//r7MF0Na0HxD2BHnjWsDKuI72Z5UEf/Rb/8MC08XKBsjCwBihGxWxycjRcNGjNEIxJBsvRMIEOipcd9qD54g==", - "dev": true - }, - "@algolia/cache-in-memory": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.12.2.tgz", - "integrity": "sha512-opWpbBUloP1fcTG3wBDnAfcoyNXW5GFDgGtLXrSANdfnelPKkr3O8j01ZTkRlPIuBDR0izGZG8MVWMDlTf71Bw==", - "dev": true, - "requires": { - "@algolia/cache-common": "4.12.2" - } - }, - "@algolia/client-account": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.12.2.tgz", - "integrity": "sha512-HZqEyeVVjzOlfoSUyc+7+ueEJmRgqSuC+hqQOGECYa5JVno4d8eRVuDAMOb87I2LOdg/WoFMcAtaaRq2gpfV/w==", - "dev": true, - "requires": { - "@algolia/client-common": "4.12.2", - "@algolia/client-search": "4.12.2", - "@algolia/transporter": "4.12.2" - } - }, - "@algolia/client-analytics": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.12.2.tgz", - "integrity": "sha512-7ktimzesu+vk3l+eG9w/nQh6/9AoIieCKmoiRIguKh6okGsaSBrcTHvUwIQEIiliqPuAFBk2M8eXYFqOZzwCZw==", - "dev": true, - "requires": { - "@algolia/client-common": "4.12.2", - "@algolia/client-search": "4.12.2", - "@algolia/requester-common": "4.12.2", - "@algolia/transporter": "4.12.2" - } - }, - "@algolia/client-common": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.12.2.tgz", - "integrity": "sha512-+dTicT1lklwOpeoiDspUoRSQYHhrr2IzllrX89/WuTPEBm2eww1xurqrSTQYC0MuVeX1s9/i4k34Q0ZnspypWg==", - "dev": true, - "requires": { - "@algolia/requester-common": "4.12.2", - "@algolia/transporter": "4.12.2" - } - }, - "@algolia/client-personalization": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.12.2.tgz", - "integrity": "sha512-JBW3vYFGIm5sAAy3cLUdmUCpmSAdreo5S1fERg7xgF6KyxGrwyy5BViTNWrOKG+av2yusk1wKydOYJ1Fbpbaxw==", - "dev": true, - "requires": { - "@algolia/client-common": "4.12.2", - "@algolia/requester-common": "4.12.2", - "@algolia/transporter": "4.12.2" - } - }, - "@algolia/client-search": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.12.2.tgz", - "integrity": "sha512-JIqi14TgfEqAooNbSPBC1ZCk3Pnviqlaz9KofAqWBxSRTpPUFnU/XQCU5ihR0PC68SFVDnU/Y9cak/XotXPUeg==", - "dev": true, - "requires": { - "@algolia/client-common": "4.12.2", - "@algolia/requester-common": "4.12.2", - "@algolia/transporter": "4.12.2" - } - }, - "@algolia/logger-common": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.12.2.tgz", - "integrity": "sha512-iOiJAymLjq137G7+8EQuUEkrgta0cZGMg6scp8s4hJ+X6k+6By4nyptdkCWYwKLsW/Xy927QcIhGlkWV78vQIQ==", - "dev": true - }, - "@algolia/logger-console": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.12.2.tgz", - "integrity": "sha512-veuQZyTSqHoHJtr9mLMnYeal9Mee6hCie4eqY+645VbeOrgT9p/kCMbKg5GLJGoLPlXGu7C0XpHyUj5k7/NQyw==", - "dev": true, - "requires": { - "@algolia/logger-common": "4.12.2" - } - }, - "@algolia/requester-browser-xhr": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.12.2.tgz", - "integrity": "sha512-FpFdHNd81tS3zj6Glqd+lt+RV0ljPExKtx+QB+gani6HWZ9YlSCM+Zl82T4ibxN+hmkrMeAyT+TMzS0jiGhGyQ==", - "dev": true, - "requires": { - "@algolia/requester-common": "4.12.2" - } - }, - "@algolia/requester-common": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.12.2.tgz", - "integrity": "sha512-4szj/lvDQf/u8EyyRBBRZD1ZkKDyLBbckLj7meQDlnbfwnW1UpLwpB2l3XJ9wDmDSftGxUCeTl5oMFe4z9OEvQ==", - "dev": true - }, - "@algolia/requester-node-http": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.12.2.tgz", - "integrity": "sha512-UXfJNZt2KMwjBjiOa3cJ/PyoXWZa/F1vy6rdyG4xQeZDcLbqKP3O2b+bOJcGPmFbmdwBhtAyMVLt+hvAvAVfOw==", - "dev": true, - "requires": { - "@algolia/requester-common": "4.12.2" - } - }, - "@algolia/transporter": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.12.2.tgz", - "integrity": "sha512-PUq79if4CukXsm27ymTQ3eD3juSvMcyJmt6mxCkSFE0zQRL4ert61HBlNH6S9y/quUVe3g7oggfHq3d5pdpqZA==", - "dev": true, - "requires": { - "@algolia/cache-common": "4.12.2", - "@algolia/logger-common": "4.12.2", - "@algolia/requester-common": "4.12.2" - } - }, - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/parser": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", - "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==", - "dev": true - }, - "@docsearch/css": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.0.0.tgz", - "integrity": "sha512-1kkV7tkAsiuEd0shunYRByKJe3xQDG2q7wYg24SOw1nV9/2lwEd4WrUYRJC/ukGTl2/kHeFxsaUvtiOy0y6fFA==", - "dev": true - }, - "@docsearch/js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.0.0.tgz", - "integrity": "sha512-j3tUJWlgW3slYqzGB8fm7y05kh2qqrIK1dZOXHeMUm/5gdKE85fiz/ltfCPMDFb/MXF+bLZChJXSMzqY0Ck30Q==", - "dev": true, - "requires": { - "@docsearch/react": "3.0.0", - "preact": "^10.0.0" - } - }, - "@docsearch/react": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.0.0.tgz", - "integrity": "sha512-yhMacqS6TVQYoBh/o603zszIb5Bl8MIXuOc6Vy617I74pirisDzzcNh0NEaYQt50fVVR3khUbeEhUEWEWipESg==", - "dev": true, - "requires": { - "@algolia/autocomplete-core": "1.5.2", - "@algolia/autocomplete-preset-algolia": "1.5.2", - "@docsearch/css": "3.0.0", - "algoliasearch": "^4.0.0" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, - "@types/prop-types": { - "version": "15.7.4", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", - "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", - "dev": true, - "peer": true - }, - "@types/react": { - "version": "17.0.39", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.39.tgz", - "integrity": "sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==", - "dev": true, - "peer": true, - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "dev": true, - "peer": true - }, - "@vitejs/plugin-vue": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-2.2.4.tgz", - "integrity": "sha512-ev9AOlp0ljCaDkFZF3JwC/pD2N4Hh+r5srl5JHM6BKg5+99jiiK0rE/XaRs3pVm1wzyKkjUy/StBSoXX5fFzcw==", - "dev": true, - "requires": {} - }, - "@vue/compiler-core": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.31.tgz", - "integrity": "sha512-aKno00qoA4o+V/kR6i/pE+aP+esng5siNAVQ422TkBNM6qA4veXiZbSe8OTXHXquEi/f6Akc+nLfB4JGfe4/WQ==", - "dev": true, - "requires": { - "@babel/parser": "^7.16.4", - "@vue/shared": "3.2.31", - "estree-walker": "^2.0.2", - "source-map": "^0.6.1" - } - }, - "@vue/compiler-dom": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.31.tgz", - "integrity": "sha512-60zIlFfzIDf3u91cqfqy9KhCKIJgPeqxgveH2L+87RcGU/alT6BRrk5JtUso0OibH3O7NXuNOQ0cDc9beT0wrg==", - "dev": true, - "requires": { - "@vue/compiler-core": "3.2.31", - "@vue/shared": "3.2.31" - } - }, - "@vue/compiler-sfc": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.31.tgz", - "integrity": "sha512-748adc9msSPGzXgibHiO6T7RWgfnDcVQD+VVwYgSsyyY8Ans64tALHZANrKtOzvkwznV/F4H7OAod/jIlp/dkQ==", - "dev": true, - "requires": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.31", - "@vue/compiler-dom": "3.2.31", - "@vue/compiler-ssr": "3.2.31", - "@vue/reactivity-transform": "3.2.31", - "@vue/shared": "3.2.31", - "estree-walker": "^2.0.2", - "magic-string": "^0.25.7", - "postcss": "^8.1.10", - "source-map": "^0.6.1" - } - }, - "@vue/compiler-ssr": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.31.tgz", - "integrity": "sha512-mjN0rqig+A8TVDnsGPYJM5dpbjlXeHUm2oZHZwGyMYiGT/F4fhJf/cXy8QpjnLQK4Y9Et4GWzHn9PS8AHUnSkw==", - "dev": true, - "requires": { - "@vue/compiler-dom": "3.2.31", - "@vue/shared": "3.2.31" - } - }, - "@vue/reactivity": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.31.tgz", - "integrity": "sha512-HVr0l211gbhpEKYr2hYe7hRsV91uIVGFYNHj73njbARVGHQvIojkImKMaZNDdoDZOIkMsBc9a1sMqR+WZwfSCw==", - "dev": true, - "requires": { - "@vue/shared": "3.2.31" - } - }, - "@vue/reactivity-transform": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.31.tgz", - "integrity": "sha512-uS4l4z/W7wXdI+Va5pgVxBJ345wyGFKvpPYtdSgvfJfX/x2Ymm6ophQlXXB6acqGHtXuBqNyyO3zVp9b1r0MOA==", - "dev": true, - "requires": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.31", - "@vue/shared": "3.2.31", - "estree-walker": "^2.0.2", - "magic-string": "^0.25.7" - } - }, - "@vue/runtime-core": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.31.tgz", - "integrity": "sha512-Kcog5XmSY7VHFEMuk4+Gap8gUssYMZ2+w+cmGI6OpZWYOEIcbE0TPzzPHi+8XTzAgx1w/ZxDFcXhZeXN5eKWsA==", - "dev": true, - "requires": { - "@vue/reactivity": "3.2.31", - "@vue/shared": "3.2.31" - } - }, - "@vue/runtime-dom": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.31.tgz", - "integrity": "sha512-N+o0sICVLScUjfLG7u9u5XCjvmsexAiPt17GNnaWHJUfsKed5e85/A3SWgKxzlxx2SW/Hw7RQxzxbXez9PtY3g==", - "dev": true, - "requires": { - "@vue/runtime-core": "3.2.31", - "@vue/shared": "3.2.31", - "csstype": "^2.6.8" - }, - "dependencies": { - "csstype": { - "version": "2.6.20", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz", - "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==", - "dev": true - } - } - }, - "@vue/server-renderer": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.31.tgz", - "integrity": "sha512-8CN3Zj2HyR2LQQBHZ61HexF5NReqngLT3oahyiVRfSSvak+oAvVmu8iNLSu6XR77Ili2AOpnAt1y8ywjjqtmkg==", - "dev": true, - "requires": { - "@vue/compiler-ssr": "3.2.31", - "@vue/shared": "3.2.31" - } - }, - "@vue/shared": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.31.tgz", - "integrity": "sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ==", - "dev": true - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "dev": true, - "requires": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "algoliasearch": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.12.2.tgz", - "integrity": "sha512-bn1P9+V415zeDQJtXn+1SwuwedEAv9/LJAxt8XwR6ygH/sMwaHSm2hpkz8wIbCBt/tKQ43TL672Kyxzv5PwGgQ==", - "dev": true, - "requires": { - "@algolia/cache-browser-local-storage": "4.12.2", - "@algolia/cache-common": "4.12.2", - "@algolia/cache-in-memory": "4.12.2", - "@algolia/client-account": "4.12.2", - "@algolia/client-analytics": "4.12.2", - "@algolia/client-common": "4.12.2", - "@algolia/client-personalization": "4.12.2", - "@algolia/client-search": "4.12.2", - "@algolia/logger-common": "4.12.2", - "@algolia/logger-console": "4.12.2", - "@algolia/requester-browser-xhr": "4.12.2", - "@algolia/requester-common": "4.12.2", - "@algolia/requester-node-http": "4.12.2", - "@algolia/transporter": "4.12.2" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.1.tgz", - "integrity": "sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==", - "dev": true - }, - "autoprefixer": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz", - "integrity": "sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==", - "dev": true, - "peer": true, - "requires": { - "browserslist": "^4.19.1", - "caniuse-lite": "^1.0.30001297", - "fraction.js": "^4.1.2", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - } - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browserslist": { - "version": "4.19.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.3.tgz", - "integrity": "sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg==", - "dev": true, - "peer": true, - "requires": { - "caniuse-lite": "^1.0.30001312", - "electron-to-chromium": "^1.4.71", - "escalade": "^3.1.1", - "node-releases": "^2.0.2", - "picocolors": "^1.0.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001313", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001313.tgz", - "integrity": "sha512-rI1UN0koZUiKINjysQDuRi2VeSCce3bYJNmDcj3PIKREiAmjakugBul1QSkg/fPrlULYl6oWfGg3PbgOSY9X4Q==", - "dev": true, - "peer": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true - }, - "csstype": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", - "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", - "dev": true, - "peer": true - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, - "detective": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", - "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", - "dev": true, - "requires": { - "acorn-node": "^1.6.1", - "defined": "^1.0.0", - "minimist": "^1.1.1" - } - }, - "didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true - }, - "dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.4.75", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.75.tgz", - "integrity": "sha512-LxgUNeu3BVU7sXaKjUDD9xivocQLxFtq6wgERrutdY/yIOps3ODOZExK1jg8DTEg4U8TUCb5MLGeWFOYuxjF3Q==", - "dev": true, - "peer": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "esbuild": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.25.tgz", - "integrity": "sha512-4JHEIOMNFvK09ziiL+iVmldIhLbn49V4NAVo888tcGFKedEZY/Y8YapfStJ6zSE23tzYPKxqKwQBnQoIO0BI/Q==", - "dev": true, - "requires": { - "esbuild-android-64": "0.14.25", - "esbuild-android-arm64": "0.14.25", - "esbuild-darwin-64": "0.14.25", - "esbuild-darwin-arm64": "0.14.25", - "esbuild-freebsd-64": "0.14.25", - "esbuild-freebsd-arm64": "0.14.25", - "esbuild-linux-32": "0.14.25", - "esbuild-linux-64": "0.14.25", - "esbuild-linux-arm": "0.14.25", - "esbuild-linux-arm64": "0.14.25", - "esbuild-linux-mips64le": "0.14.25", - "esbuild-linux-ppc64le": "0.14.25", - "esbuild-linux-riscv64": "0.14.25", - "esbuild-linux-s390x": "0.14.25", - "esbuild-netbsd-64": "0.14.25", - "esbuild-openbsd-64": "0.14.25", - "esbuild-sunos-64": "0.14.25", - "esbuild-windows-32": "0.14.25", - "esbuild-windows-64": "0.14.25", - "esbuild-windows-arm64": "0.14.25" - } - }, - "esbuild-android-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.25.tgz", - "integrity": "sha512-L5vCUk7TzFbBnoESNoXjU3x9+/+7TDIE/1mTfy/erAfvZAqC+S3sp/Qa9wkypFMcFvN9FzvESkTlpeQDolREtQ==", - "dev": true, - "optional": true - }, - "esbuild-android-arm64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.25.tgz", - "integrity": "sha512-4jv5xPjM/qNm27T5j3ZEck0PvjgQtoMHnz4FzwF5zNP56PvY2CT0WStcAIl6jNlsuDdN63rk2HRBIsO6xFbcFw==", - "dev": true, - "optional": true - }, - "esbuild-darwin-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.25.tgz", - "integrity": "sha512-TGp8tuudIxOyWd1+8aYPxQmC1ZQyvij/AfNBa35RubixD0zJ1vkKHVAzo0Zao1zcG6pNqiSyzfPto8vmg0s7oA==", - "dev": true, - "optional": true - }, - "esbuild-darwin-arm64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.25.tgz", - "integrity": "sha512-oTcDgdm0MDVEmw2DWu8BV68pYuImpFgvWREPErBZmNA4MYKGuBRaCiJqq6jZmBR1x+3y1DWCjez+5uLtuAm6mw==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.25.tgz", - "integrity": "sha512-ueAqbnMZ8arnuLH8tHwTCQYeptnHOUV7vA6px6j4zjjQwDx7TdP7kACPf3TLZLdJQ3CAD1XCvQ2sPhX+8tacvQ==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-arm64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.25.tgz", - "integrity": "sha512-+ZVWud2HKh+Ob6k/qiJWjBtUg4KmJGGmbvEXXW1SNKS7hW7HU+Zq2ZCcE1akFxOPkVB+EhOty/sSek30tkCYug==", - "dev": true, - "optional": true - }, - "esbuild-linux-32": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.25.tgz", - "integrity": "sha512-3OP/lwV3kCzEz45tobH9nj+uE4ubhGsfx+tn0L26WAGtUbmmcRpqy7XRG/qK7h1mClZ+eguIANcQntYMdYklfw==", - "dev": true, - "optional": true - }, - "esbuild-linux-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.25.tgz", - "integrity": "sha512-+aKHdHZmX9qwVlQmu5xYXh7GsBFf4TWrePgeJTalhXHOG7NNuUwoHmketGiZEoNsWyyqwH9rE5BC+iwcLY30Ug==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.25.tgz", - "integrity": "sha512-aTLcE2VBoLydL943REcAcgnDi3bHtmULSXWLbjtBdtykRatJVSxKMjK9YlBXUZC4/YcNQfH7AxwVeQr9fNxPhw==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.25.tgz", - "integrity": "sha512-UxfenPx/wSZx55gScCImPtXekvZQLI2GW3qe5dtlmU7luiqhp5GWPzGeQEbD3yN3xg/pHc671m5bma5Ns7lBHw==", - "dev": true, - "optional": true - }, - "esbuild-linux-mips64le": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.25.tgz", - "integrity": "sha512-wLWYyqVfYx9Ur6eU5RT92yJVsaBGi5RdkoWqRHOqcJ38Kn60QMlcghsKeWfe9jcYut8LangYZ98xO1LxIoSXrQ==", - "dev": true, - "optional": true - }, - "esbuild-linux-ppc64le": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.25.tgz", - "integrity": "sha512-0dR6Csl6Zas3g4p9ULckEl8Mo8IInJh33VCJ3eaV1hj9+MHGdmDOakYMN8MZP9/5nl+NU/0ygpd14cWgy8uqRw==", - "dev": true, - "optional": true - }, - "esbuild-linux-riscv64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.25.tgz", - "integrity": "sha512-J4d20HDmTrgvhR0bdkDhvvJGaikH3LzXQnNaseo8rcw9Yqby9A90gKUmWpfwqLVNRILvNnAmKLfBjCKU9ajg8w==", - "dev": true, - "optional": true - }, - "esbuild-linux-s390x": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.25.tgz", - "integrity": "sha512-YI2d5V6nTE73ZnhEKQD7MtsPs1EtUZJ3obS21oxQxGbbRw1G+PtJKjNyur+3t6nzHP9oTg6GHQ3S3hOLLmbDIQ==", - "dev": true, - "optional": true - }, - "esbuild-netbsd-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.25.tgz", - "integrity": "sha512-TKIVgNWLUOkr+Exrye70XTEE1lJjdQXdM4tAXRzfHE9iBA7LXWcNtVIuSnphTqpanPzTDFarF0yqq4kpbC6miA==", - "dev": true, - "optional": true - }, - "esbuild-openbsd-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.25.tgz", - "integrity": "sha512-QgFJ37A15D7NIXBTYEqz29+uw3nNBOIyog+3kFidANn6kjw0GHZ0lEYQn+cwjyzu94WobR+fes7cTl/ZYlHb1A==", - "dev": true, - "optional": true - }, - "esbuild-sunos-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.25.tgz", - "integrity": "sha512-rmWfjUItYIVlqr5EnTH1+GCxXiBOC42WBZ3w++qh7n2cS9Xo0lO5pGSG2N+huOU2fX5L+6YUuJ78/vOYvefeFw==", - "dev": true, - "optional": true - }, - "esbuild-windows-32": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.25.tgz", - "integrity": "sha512-HGAxVUofl3iUIz9W10Y9XKtD0bNsK9fBXv1D55N/ljNvkrAYcGB8YCm0v7DjlwtyS6ws3dkdQyXadbxkbzaKOA==", - "dev": true, - "optional": true - }, - "esbuild-windows-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.25.tgz", - "integrity": "sha512-TirEohRkfWU9hXLgoDxzhMQD1g8I2mOqvdQF2RS9E/wbkORTAqJHyh7wqGRCQAwNzdNXdg3JAyhQ9/177AadWA==", - "dev": true, - "optional": true - }, - "esbuild-windows-arm64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.25.tgz", - "integrity": "sha512-4ype9ERiI45rSh+R8qUoBtaj6kJvUOI7oVLhKqPEpcF4Pa5PpT3hm/mXAyotJHREkHpM87PAJcA442mLnbtlNA==", - "dev": true, - "optional": true - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "peer": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "fraction.js": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.3.tgz", - "integrity": "sha512-pUHWWt6vHzZZiQJcM6S/0PXfS+g6FM4BF5rj9wZyreivhQPdsh5PpE25VtSNxq80wHS5RfY51Ii+8Z0Zl/pmzg==", - "dev": true, - "peer": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "lilconfig": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", - "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", - "dev": true - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "peer": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.8" - } - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", - "dev": true - }, - "node-releases": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", - "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", - "dev": true, - "peer": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true, - "peer": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "peer": true - }, - "object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "postcss": { - "version": "8.4.7", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.7.tgz", - "integrity": "sha512-L9Ye3r6hkkCeOETQX6iOaWZgjp3LL6Lpqm6EtgbKrgqGGteRMNb9vzBfRL96YOSu8o7x3MfIH9Mo5cPJFGrW6A==", - "dev": true, - "requires": { - "nanoid": "^3.3.1", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "postcss-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz", - "integrity": "sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==", - "dev": true, - "requires": { - "camelcase-css": "^2.0.1" - } - }, - "postcss-load-config": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.3.tgz", - "integrity": "sha512-5EYgaM9auHGtO//ljHH+v/aC/TQ5LHXtL7bQajNAUBKUVKiYE8rYpFms7+V26D9FncaGe2zwCoPQsFKb5zF/Hw==", - "dev": true, - "requires": { - "lilconfig": "^2.0.4", - "yaml": "^1.10.2" - } - }, - "postcss-nested": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz", - "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.6" - } - }, - "postcss-selector-parser": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz", - "integrity": "sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "preact": { - "version": "10.6.6", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.6.6.tgz", - "integrity": "sha512-dgxpTFV2vs4vizwKohYKkk7g7rmp1wOOcfd4Tz3IB3Wi+ivZzsn/SpeKJhRENSE+n8sUfsAl4S3HiCVT923ABw==", - "dev": true - }, - "prismjs": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", - "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true - }, - "react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "dev": true, - "peer": true, - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "dev": true, - "peer": true, - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", - "dev": true, - "requires": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rollup": { - "version": "2.69.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.69.1.tgz", - "integrity": "sha512-xaQKTomUVZBopk38EIshM/kOoPFkKWisgBV7Emy80coP9MOSLUDrba1jKZhqH0iS5DoGcRbbcuyl/BzblV8w5w==", - "dev": true, - "requires": { - "fsevents": "~2.3.2" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "dev": true, - "peer": true, - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "tailwindcss": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.0.23.tgz", - "integrity": "sha512-+OZOV9ubyQ6oI2BXEhzw4HrqvgcARY38xv3zKcjnWtMIZstEsXdI9xftd1iB7+RbOnj2HOEzkA0OyB5BaSxPQA==", - "dev": true, - "requires": { - "arg": "^5.0.1", - "chalk": "^4.1.2", - "chokidar": "^3.5.3", - "color-name": "^1.1.4", - "cosmiconfig": "^7.0.1", - "detective": "^5.2.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.2.11", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "normalize-path": "^3.0.0", - "object-hash": "^2.2.0", - "postcss": "^8.4.6", - "postcss-js": "^4.0.0", - "postcss-load-config": "^3.1.0", - "postcss-nested": "5.0.6", - "postcss-selector-parser": "^6.0.9", - "postcss-value-parser": "^4.2.0", - "quick-lru": "^5.1.1", - "resolve": "^1.22.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "vite": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-2.8.6.tgz", - "integrity": "sha512-e4H0QpludOVKkmOsRyqQ7LTcMUDF3mcgyNU4lmi0B5JUbe0ZxeBBl8VoZ8Y6Rfn9eFKYtdXNPcYK97ZwH+K2ug==", - "dev": true, - "requires": { - "esbuild": "^0.14.14", - "fsevents": "~2.3.2", - "postcss": "^8.4.6", - "resolve": "^1.22.0", - "rollup": "^2.59.0" - } - }, - "vitepress": { - "version": "0.22.3", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-0.22.3.tgz", - "integrity": "sha512-Yfvu/rent2vp/TXIDZMutS6ft2TJPn4xngS48PYFWDEbuFI2ccUAXM481lF1qVVnCKxfh4g8e/KPvevSJdg1Bw==", - "dev": true, - "requires": { - "@docsearch/css": "^3.0.0-alpha.41", - "@docsearch/js": "^3.0.0-alpha.41", - "@vitejs/plugin-vue": "^2.2.0", - "prismjs": "^1.25.0", - "vite": "^2.8.1", - "vue": "^3.2.31" - } - }, - "vue": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.31.tgz", - "integrity": "sha512-odT3W2tcffTiQCy57nOT93INw1auq5lYLLYtWpPYQQYQOOdHiqFct9Xhna6GJ+pJQaF67yZABraH47oywkJgFw==", - "dev": true, - "requires": { - "@vue/compiler-dom": "3.2.31", - "@vue/compiler-sfc": "3.2.31", - "@vue/runtime-dom": "3.2.31", - "@vue/server-renderer": "3.2.31", - "@vue/shared": "3.2.31" - } - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true - } - } -} diff --git a/docs/package.json b/docs/package.json index da849551..9258fab6 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,21 +1,21 @@ { "name": "castopod-docs", - "version": "0.0.0-development", - "description": "Castopod documentation using vitepress", - "main": "index.js", + "type": "module", + "version": "0.0.1", "scripts": { - "dev": "vitepress --port=3050", - "build": "vitepress build", - "serve": "vitepress serve" + "dev": "astro dev", + "start": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro", + "prepare": "astro telemetry disable" }, - "author": { - "name": "Yassine Doghri", - "email": "yassine@doghri.fr", - "url": "https://code.castopod.org/yassine" - }, - "license": "AGPL-3.0-or-later", - "devDependencies": { - "tailwindcss": "^3.0.23", - "vitepress": "^0.22.3" + "dependencies": { + "@astrojs/starlight": "^0.37.6", + "@fontsource/inter": "^5.2.8", + "@fontsource/rubik": "^5.2.8", + "astro": "^5.17.2", + "sharp": "^0.34.5", + "starlight-openapi": "^0.22.0" } } diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml new file mode 100644 index 00000000..45dac61d --- /dev/null +++ b/docs/pnpm-lock.yaml @@ -0,0 +1,6029 @@ +lockfileVersion: "9.0" + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + .: + dependencies: + "@astrojs/starlight": + specifier: ^0.37.6 + version: 0.37.6(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3)) + "@fontsource/inter": + specifier: ^5.2.8 + version: 5.2.8 + "@fontsource/rubik": + specifier: ^5.2.8 + version: 5.2.8 + astro: + specifier: ^5.17.2 + version: 5.17.2(rollup@4.57.1)(typescript@5.9.3) + sharp: + specifier: ^0.34.5 + version: 0.34.5 + starlight-openapi: + specifier: ^0.22.0 + version: 0.22.0(@astrojs/markdown-remark@6.3.10)(@astrojs/starlight@0.37.6(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3)))(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3))(openapi-types@12.1.3) + +packages: + "@apidevtools/json-schema-ref-parser@13.0.5": + resolution: + { + integrity: sha512-xfh4xVJD62gG6spIc7lwxoWT+l16nZu1ELyU8FkjaP/oD2yP09EvLAU6KhtudN9aML2Khhs9pY6Slr7KGTES3w==, + } + engines: { node: ">= 16" } + + "@astrojs/compiler@2.13.1": + resolution: + { + integrity: sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==, + } + + "@astrojs/internal-helpers@0.7.5": + resolution: + { + integrity: sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==, + } + + "@astrojs/markdown-remark@6.3.10": + resolution: + { + integrity: sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A==, + } + + "@astrojs/mdx@4.3.13": + resolution: + { + integrity: sha512-IHDHVKz0JfKBy3//52JSiyWv089b7GVSChIXLrlUOoTLWowG3wr2/8hkaEgEyd/vysvNQvGk+QhysXpJW5ve6Q==, + } + engines: { node: 18.20.8 || ^20.3.0 || >=22.0.0 } + peerDependencies: + astro: ^5.0.0 + + "@astrojs/prism@3.3.0": + resolution: + { + integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==, + } + engines: { node: 18.20.8 || ^20.3.0 || >=22.0.0 } + + "@astrojs/sitemap@3.7.0": + resolution: + { + integrity: sha512-+qxjUrz6Jcgh+D5VE1gKUJTA3pSthuPHe6Ao5JCxok794Lewx8hBFaWHtOnN0ntb2lfOf7gvOi9TefUswQ/ZVA==, + } + + "@astrojs/starlight@0.37.6": + resolution: + { + integrity: sha512-wQrKwH431q+8FsLBnNQeG+R36TMtEGxTQ2AuiVpcx9APcazvL3n7wVW8mMmYyxX0POjTnxlcWPkdMGR3Yj1L+w==, + } + peerDependencies: + astro: ^5.5.0 + + "@astrojs/telemetry@3.3.0": + resolution: + { + integrity: sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==, + } + engines: { node: 18.20.8 || ^20.3.0 || >=22.0.0 } + + "@babel/code-frame@7.29.0": + resolution: + { + integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-string-parser@7.27.1": + resolution: + { + integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-validator-identifier@7.28.5": + resolution: + { + integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==, + } + engines: { node: ">=6.9.0" } + + "@babel/parser@7.29.0": + resolution: + { + integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==, + } + engines: { node: ">=6.0.0" } + hasBin: true + + "@babel/runtime@7.28.6": + resolution: + { + integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==, + } + engines: { node: ">=6.9.0" } + + "@babel/types@7.29.0": + resolution: + { + integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==, + } + engines: { node: ">=6.9.0" } + + "@capsizecss/unpack@4.0.0": + resolution: + { + integrity: sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==, + } + engines: { node: ">=18" } + + "@ctrl/tinycolor@4.2.0": + resolution: + { + integrity: sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==, + } + engines: { node: ">=14" } + + "@emnapi/runtime@1.8.1": + resolution: + { + integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==, + } + + "@esbuild/aix-ppc64@0.25.12": + resolution: + { + integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [aix] + + "@esbuild/aix-ppc64@0.27.3": + resolution: + { + integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [aix] + + "@esbuild/android-arm64@0.25.12": + resolution: + { + integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [android] + + "@esbuild/android-arm64@0.27.3": + resolution: + { + integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [android] + + "@esbuild/android-arm@0.25.12": + resolution: + { + integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [android] + + "@esbuild/android-arm@0.27.3": + resolution: + { + integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [android] + + "@esbuild/android-x64@0.25.12": + resolution: + { + integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [android] + + "@esbuild/android-x64@0.27.3": + resolution: + { + integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [android] + + "@esbuild/darwin-arm64@0.25.12": + resolution: + { + integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [darwin] + + "@esbuild/darwin-arm64@0.27.3": + resolution: + { + integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [darwin] + + "@esbuild/darwin-x64@0.25.12": + resolution: + { + integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [darwin] + + "@esbuild/darwin-x64@0.27.3": + resolution: + { + integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [darwin] + + "@esbuild/freebsd-arm64@0.25.12": + resolution: + { + integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [freebsd] + + "@esbuild/freebsd-arm64@0.27.3": + resolution: + { + integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [freebsd] + + "@esbuild/freebsd-x64@0.25.12": + resolution: + { + integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [freebsd] + + "@esbuild/freebsd-x64@0.27.3": + resolution: + { + integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [freebsd] + + "@esbuild/linux-arm64@0.25.12": + resolution: + { + integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [linux] + + "@esbuild/linux-arm64@0.27.3": + resolution: + { + integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [linux] + + "@esbuild/linux-arm@0.25.12": + resolution: + { + integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [linux] + + "@esbuild/linux-arm@0.27.3": + resolution: + { + integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [linux] + + "@esbuild/linux-ia32@0.25.12": + resolution: + { + integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [linux] + + "@esbuild/linux-ia32@0.27.3": + resolution: + { + integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [linux] + + "@esbuild/linux-loong64@0.25.12": + resolution: + { + integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==, + } + engines: { node: ">=18" } + cpu: [loong64] + os: [linux] + + "@esbuild/linux-loong64@0.27.3": + resolution: + { + integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==, + } + engines: { node: ">=18" } + cpu: [loong64] + os: [linux] + + "@esbuild/linux-mips64el@0.25.12": + resolution: + { + integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==, + } + engines: { node: ">=18" } + cpu: [mips64el] + os: [linux] + + "@esbuild/linux-mips64el@0.27.3": + resolution: + { + integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==, + } + engines: { node: ">=18" } + cpu: [mips64el] + os: [linux] + + "@esbuild/linux-ppc64@0.25.12": + resolution: + { + integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [linux] + + "@esbuild/linux-ppc64@0.27.3": + resolution: + { + integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [linux] + + "@esbuild/linux-riscv64@0.25.12": + resolution: + { + integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==, + } + engines: { node: ">=18" } + cpu: [riscv64] + os: [linux] + + "@esbuild/linux-riscv64@0.27.3": + resolution: + { + integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==, + } + engines: { node: ">=18" } + cpu: [riscv64] + os: [linux] + + "@esbuild/linux-s390x@0.25.12": + resolution: + { + integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==, + } + engines: { node: ">=18" } + cpu: [s390x] + os: [linux] + + "@esbuild/linux-s390x@0.27.3": + resolution: + { + integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==, + } + engines: { node: ">=18" } + cpu: [s390x] + os: [linux] + + "@esbuild/linux-x64@0.25.12": + resolution: + { + integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [linux] + + "@esbuild/linux-x64@0.27.3": + resolution: + { + integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [linux] + + "@esbuild/netbsd-arm64@0.25.12": + resolution: + { + integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [netbsd] + + "@esbuild/netbsd-arm64@0.27.3": + resolution: + { + integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [netbsd] + + "@esbuild/netbsd-x64@0.25.12": + resolution: + { + integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [netbsd] + + "@esbuild/netbsd-x64@0.27.3": + resolution: + { + integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [netbsd] + + "@esbuild/openbsd-arm64@0.25.12": + resolution: + { + integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [openbsd] + + "@esbuild/openbsd-arm64@0.27.3": + resolution: + { + integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [openbsd] + + "@esbuild/openbsd-x64@0.25.12": + resolution: + { + integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [openbsd] + + "@esbuild/openbsd-x64@0.27.3": + resolution: + { + integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [openbsd] + + "@esbuild/openharmony-arm64@0.25.12": + resolution: + { + integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [openharmony] + + "@esbuild/openharmony-arm64@0.27.3": + resolution: + { + integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [openharmony] + + "@esbuild/sunos-x64@0.25.12": + resolution: + { + integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [sunos] + + "@esbuild/sunos-x64@0.27.3": + resolution: + { + integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [sunos] + + "@esbuild/win32-arm64@0.25.12": + resolution: + { + integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [win32] + + "@esbuild/win32-arm64@0.27.3": + resolution: + { + integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [win32] + + "@esbuild/win32-ia32@0.25.12": + resolution: + { + integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [win32] + + "@esbuild/win32-ia32@0.27.3": + resolution: + { + integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [win32] + + "@esbuild/win32-x64@0.25.12": + resolution: + { + integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [win32] + + "@esbuild/win32-x64@0.27.3": + resolution: + { + integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [win32] + + "@expressive-code/core@0.41.6": + resolution: + { + integrity: sha512-FvJQP+hG0jWi/FLBSmvHInDqWR7jNANp9PUDjdMqSshHb0y7sxx3vHuoOr6SgXjWw+MGLqorZyPQ0aAlHEok6g==, + } + + "@expressive-code/plugin-frames@0.41.6": + resolution: + { + integrity: sha512-d+hkSYXIQot6fmYnOmWAM+7TNWRv/dhfjMsNq+mIZz8Tb4mPHOcgcfZeEM5dV9TDL0ioQNvtcqQNuzA1sRPjxg==, + } + + "@expressive-code/plugin-shiki@0.41.6": + resolution: + { + integrity: sha512-Y6zmKBmsIUtWTzdefqlzm/h9Zz0Rc4gNdt2GTIH7fhHH2I9+lDYCa27BDwuBhjqcos6uK81Aca9dLUC4wzN+ng==, + } + + "@expressive-code/plugin-text-markers@0.41.6": + resolution: + { + integrity: sha512-PBFa1wGyYzRExMDzBmAWC6/kdfG1oLn4pLpBeTfIRrALPjcGA/59HP3e7q9J0Smk4pC7U+lWkA2LHR8FYV8U7Q==, + } + + "@fontsource/inter@5.2.8": + resolution: + { + integrity: sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==, + } + + "@fontsource/rubik@5.2.8": + resolution: + { + integrity: sha512-PIc8QR7FqWPcYhbdRiGff56vQlKqg/ytES1YqecSq1GkgxiH4TBshrFvDEOZ9JonUF9m1qQ+qXxJj7wD5zgXEw==, + } + + "@humanwhocodes/momoa@2.0.4": + resolution: + { + integrity: sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==, + } + engines: { node: ">=10.10.0" } + + "@img/colour@1.0.0": + resolution: + { + integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==, + } + engines: { node: ">=18" } + + "@img/sharp-darwin-arm64@0.34.5": + resolution: + { + integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [darwin] + + "@img/sharp-darwin-x64@0.34.5": + resolution: + { + integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [darwin] + + "@img/sharp-libvips-darwin-arm64@1.2.4": + resolution: + { + integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==, + } + cpu: [arm64] + os: [darwin] + + "@img/sharp-libvips-darwin-x64@1.2.4": + resolution: + { + integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==, + } + cpu: [x64] + os: [darwin] + + "@img/sharp-libvips-linux-arm64@1.2.4": + resolution: + { + integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==, + } + cpu: [arm64] + os: [linux] + libc: [glibc] + + "@img/sharp-libvips-linux-arm@1.2.4": + resolution: + { + integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==, + } + cpu: [arm] + os: [linux] + libc: [glibc] + + "@img/sharp-libvips-linux-ppc64@1.2.4": + resolution: + { + integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==, + } + cpu: [ppc64] + os: [linux] + libc: [glibc] + + "@img/sharp-libvips-linux-riscv64@1.2.4": + resolution: + { + integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==, + } + cpu: [riscv64] + os: [linux] + libc: [glibc] + + "@img/sharp-libvips-linux-s390x@1.2.4": + resolution: + { + integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==, + } + cpu: [s390x] + os: [linux] + libc: [glibc] + + "@img/sharp-libvips-linux-x64@1.2.4": + resolution: + { + integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==, + } + cpu: [x64] + os: [linux] + libc: [glibc] + + "@img/sharp-libvips-linuxmusl-arm64@1.2.4": + resolution: + { + integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==, + } + cpu: [arm64] + os: [linux] + libc: [musl] + + "@img/sharp-libvips-linuxmusl-x64@1.2.4": + resolution: + { + integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==, + } + cpu: [x64] + os: [linux] + libc: [musl] + + "@img/sharp-linux-arm64@0.34.5": + resolution: + { + integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [linux] + libc: [glibc] + + "@img/sharp-linux-arm@0.34.5": + resolution: + { + integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm] + os: [linux] + libc: [glibc] + + "@img/sharp-linux-ppc64@0.34.5": + resolution: + { + integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [ppc64] + os: [linux] + libc: [glibc] + + "@img/sharp-linux-riscv64@0.34.5": + resolution: + { + integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [riscv64] + os: [linux] + libc: [glibc] + + "@img/sharp-linux-s390x@0.34.5": + resolution: + { + integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [s390x] + os: [linux] + libc: [glibc] + + "@img/sharp-linux-x64@0.34.5": + resolution: + { + integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [linux] + libc: [glibc] + + "@img/sharp-linuxmusl-arm64@0.34.5": + resolution: + { + integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [linux] + libc: [musl] + + "@img/sharp-linuxmusl-x64@0.34.5": + resolution: + { + integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [linux] + libc: [musl] + + "@img/sharp-wasm32@0.34.5": + resolution: + { + integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [wasm32] + + "@img/sharp-win32-arm64@0.34.5": + resolution: + { + integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [win32] + + "@img/sharp-win32-ia32@0.34.5": + resolution: + { + integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [ia32] + os: [win32] + + "@img/sharp-win32-x64@0.34.5": + resolution: + { + integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [win32] + + "@jridgewell/sourcemap-codec@1.5.5": + resolution: + { + integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==, + } + + "@mdx-js/mdx@3.1.1": + resolution: + { + integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==, + } + + "@oslojs/encoding@1.1.0": + resolution: + { + integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==, + } + + "@pagefind/darwin-arm64@1.4.0": + resolution: + { + integrity: sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==, + } + cpu: [arm64] + os: [darwin] + + "@pagefind/darwin-x64@1.4.0": + resolution: + { + integrity: sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==, + } + cpu: [x64] + os: [darwin] + + "@pagefind/default-ui@1.4.0": + resolution: + { + integrity: sha512-wie82VWn3cnGEdIjh4YwNESyS1G6vRHwL6cNjy9CFgNnWW/PGRjsLq300xjVH5sfPFK3iK36UxvIBymtQIEiSQ==, + } + + "@pagefind/freebsd-x64@1.4.0": + resolution: + { + integrity: sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==, + } + cpu: [x64] + os: [freebsd] + + "@pagefind/linux-arm64@1.4.0": + resolution: + { + integrity: sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==, + } + cpu: [arm64] + os: [linux] + + "@pagefind/linux-x64@1.4.0": + resolution: + { + integrity: sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==, + } + cpu: [x64] + os: [linux] + + "@pagefind/windows-x64@1.4.0": + resolution: + { + integrity: sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==, + } + cpu: [x64] + os: [win32] + + "@readme/better-ajv-errors@2.4.0": + resolution: + { + integrity: sha512-9WODaOAKSl/mU+MYNZ2aHCrkoRSvmQ+1YkLj589OEqqjOAhbn8j7Z+ilYoiTu/he6X63/clsxxAB4qny9/dDzg==, + } + engines: { node: ">=18" } + peerDependencies: + ajv: 4.11.8 - 8 + + "@readme/openapi-parser@4.1.2": + resolution: + { + integrity: sha512-lAFH88r/CHs5VZDUocEda0OSMSQsr6801sziIjOKyVA+0hSFN+BPuelPF5XvkMROHecnPd+XEJN1iNQqCgER/g==, + } + engines: { node: ">=20" } + peerDependencies: + openapi-types: ">=7" + + "@readme/openapi-schemas@3.1.0": + resolution: + { + integrity: sha512-9FC/6ho8uFa8fV50+FPy/ngWN53jaUu4GRXlAjcxIRrzhltJnpKkBG2Tp0IDraFJeWrOpk84RJ9EMEEYzaI1Bw==, + } + engines: { node: ">=18" } + + "@rollup/pluginutils@5.3.0": + resolution: + { + integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==, + } + engines: { node: ">=14.0.0" } + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + "@rollup/rollup-android-arm-eabi@4.57.1": + resolution: + { + integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==, + } + cpu: [arm] + os: [android] + + "@rollup/rollup-android-arm64@4.57.1": + resolution: + { + integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==, + } + cpu: [arm64] + os: [android] + + "@rollup/rollup-darwin-arm64@4.57.1": + resolution: + { + integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==, + } + cpu: [arm64] + os: [darwin] + + "@rollup/rollup-darwin-x64@4.57.1": + resolution: + { + integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==, + } + cpu: [x64] + os: [darwin] + + "@rollup/rollup-freebsd-arm64@4.57.1": + resolution: + { + integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==, + } + cpu: [arm64] + os: [freebsd] + + "@rollup/rollup-freebsd-x64@4.57.1": + resolution: + { + integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==, + } + cpu: [x64] + os: [freebsd] + + "@rollup/rollup-linux-arm-gnueabihf@4.57.1": + resolution: + { + integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==, + } + cpu: [arm] + os: [linux] + libc: [glibc] + + "@rollup/rollup-linux-arm-musleabihf@4.57.1": + resolution: + { + integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==, + } + cpu: [arm] + os: [linux] + libc: [musl] + + "@rollup/rollup-linux-arm64-gnu@4.57.1": + resolution: + { + integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==, + } + cpu: [arm64] + os: [linux] + libc: [glibc] + + "@rollup/rollup-linux-arm64-musl@4.57.1": + resolution: + { + integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==, + } + cpu: [arm64] + os: [linux] + libc: [musl] + + "@rollup/rollup-linux-loong64-gnu@4.57.1": + resolution: + { + integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==, + } + cpu: [loong64] + os: [linux] + libc: [glibc] + + "@rollup/rollup-linux-loong64-musl@4.57.1": + resolution: + { + integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==, + } + cpu: [loong64] + os: [linux] + libc: [musl] + + "@rollup/rollup-linux-ppc64-gnu@4.57.1": + resolution: + { + integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==, + } + cpu: [ppc64] + os: [linux] + libc: [glibc] + + "@rollup/rollup-linux-ppc64-musl@4.57.1": + resolution: + { + integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==, + } + cpu: [ppc64] + os: [linux] + libc: [musl] + + "@rollup/rollup-linux-riscv64-gnu@4.57.1": + resolution: + { + integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==, + } + cpu: [riscv64] + os: [linux] + libc: [glibc] + + "@rollup/rollup-linux-riscv64-musl@4.57.1": + resolution: + { + integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==, + } + cpu: [riscv64] + os: [linux] + libc: [musl] + + "@rollup/rollup-linux-s390x-gnu@4.57.1": + resolution: + { + integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==, + } + cpu: [s390x] + os: [linux] + libc: [glibc] + + "@rollup/rollup-linux-x64-gnu@4.57.1": + resolution: + { + integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==, + } + cpu: [x64] + os: [linux] + libc: [glibc] + + "@rollup/rollup-linux-x64-musl@4.57.1": + resolution: + { + integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==, + } + cpu: [x64] + os: [linux] + libc: [musl] + + "@rollup/rollup-openbsd-x64@4.57.1": + resolution: + { + integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==, + } + cpu: [x64] + os: [openbsd] + + "@rollup/rollup-openharmony-arm64@4.57.1": + resolution: + { + integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==, + } + cpu: [arm64] + os: [openharmony] + + "@rollup/rollup-win32-arm64-msvc@4.57.1": + resolution: + { + integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==, + } + cpu: [arm64] + os: [win32] + + "@rollup/rollup-win32-ia32-msvc@4.57.1": + resolution: + { + integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==, + } + cpu: [ia32] + os: [win32] + + "@rollup/rollup-win32-x64-gnu@4.57.1": + resolution: + { + integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==, + } + cpu: [x64] + os: [win32] + + "@rollup/rollup-win32-x64-msvc@4.57.1": + resolution: + { + integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==, + } + cpu: [x64] + os: [win32] + + "@shikijs/core@3.22.0": + resolution: + { + integrity: sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==, + } + + "@shikijs/engine-javascript@3.22.0": + resolution: + { + integrity: sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==, + } + + "@shikijs/engine-oniguruma@3.22.0": + resolution: + { + integrity: sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==, + } + + "@shikijs/langs@3.22.0": + resolution: + { + integrity: sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==, + } + + "@shikijs/themes@3.22.0": + resolution: + { + integrity: sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==, + } + + "@shikijs/types@3.22.0": + resolution: + { + integrity: sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==, + } + + "@shikijs/vscode-textmate@10.0.2": + resolution: + { + integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==, + } + + "@types/debug@4.1.12": + resolution: + { + integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==, + } + + "@types/estree-jsx@1.0.5": + resolution: + { + integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==, + } + + "@types/estree@1.0.8": + resolution: + { + integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, + } + + "@types/hast@3.0.4": + resolution: + { + integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==, + } + + "@types/js-yaml@4.0.9": + resolution: + { + integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==, + } + + "@types/json-schema@7.0.15": + resolution: + { + integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, + } + + "@types/mdast@4.0.4": + resolution: + { + integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==, + } + + "@types/mdx@2.0.13": + resolution: + { + integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==, + } + + "@types/ms@2.1.0": + resolution: + { + integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==, + } + + "@types/nlcst@2.0.3": + resolution: + { + integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==, + } + + "@types/node@17.0.45": + resolution: + { + integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==, + } + + "@types/sax@1.2.7": + resolution: + { + integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==, + } + + "@types/unist@2.0.11": + resolution: + { + integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==, + } + + "@types/unist@3.0.3": + resolution: + { + integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==, + } + + "@ungap/structured-clone@1.3.0": + resolution: + { + integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==, + } + + acorn-jsx@5.3.2: + resolution: + { + integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, + } + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: + { + integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==, + } + engines: { node: ">=0.4.0" } + hasBin: true + + ajv-draft-04@1.0.0: + resolution: + { + integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==, + } + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.18.0: + resolution: + { + integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==, + } + + ansi-align@3.0.1: + resolution: + { + integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==, + } + + ansi-regex@5.0.1: + resolution: + { + integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, + } + engines: { node: ">=8" } + + ansi-regex@6.2.2: + resolution: + { + integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==, + } + engines: { node: ">=12" } + + ansi-styles@6.2.3: + resolution: + { + integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==, + } + engines: { node: ">=12" } + + anymatch@3.1.3: + resolution: + { + integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==, + } + engines: { node: ">= 8" } + + arg@5.0.2: + resolution: + { + integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==, + } + + argparse@2.0.1: + resolution: + { + integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, + } + + aria-query@5.3.2: + resolution: + { + integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==, + } + engines: { node: ">= 0.4" } + + array-iterate@2.0.1: + resolution: + { + integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==, + } + + astring@1.9.0: + resolution: + { + integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==, + } + hasBin: true + + astro-expressive-code@0.41.6: + resolution: + { + integrity: sha512-l47tb1uhmVIebHUkw+HEPtU/av0G4O8Q34g2cbkPvC7/e9ZhANcjUUciKt9Hp6gSVDdIuXBBLwJQn2LkeGMOAw==, + } + peerDependencies: + astro: ^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta + + astro@5.17.2: + resolution: + { + integrity: sha512-7jnMqGo53hOQNwo1N/wqeOvUp8wwW/p+DeerSjSkHNx8L/1mhy6P7rVo7EhdmF8DpKqw0tl/B5Fx1WcIzg1ysA==, + } + engines: + { node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: ">=9.6.5", pnpm: ">=7.1.0" } + hasBin: true + + axobject-query@4.1.0: + resolution: + { + integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==, + } + engines: { node: ">= 0.4" } + + bail@2.0.2: + resolution: + { + integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==, + } + + base-64@1.0.0: + resolution: + { + integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==, + } + + bcp-47-match@2.0.3: + resolution: + { + integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==, + } + + bcp-47@2.1.0: + resolution: + { + integrity: sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==, + } + + boolbase@1.0.0: + resolution: + { + integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==, + } + + boxen@8.0.1: + resolution: + { + integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==, + } + engines: { node: ">=18" } + + camelcase@8.0.0: + resolution: + { + integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==, + } + engines: { node: ">=16" } + + ccount@2.0.1: + resolution: + { + integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==, + } + + chalk@5.6.2: + resolution: + { + integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==, + } + engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } + + character-entities-html4@2.1.0: + resolution: + { + integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==, + } + + character-entities-legacy@3.0.0: + resolution: + { + integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==, + } + + character-entities@2.0.2: + resolution: + { + integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==, + } + + character-reference-invalid@2.0.1: + resolution: + { + integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==, + } + + chokidar@5.0.0: + resolution: + { + integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==, + } + engines: { node: ">= 20.19.0" } + + ci-info@4.4.0: + resolution: + { + integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==, + } + engines: { node: ">=8" } + + cli-boxes@3.0.0: + resolution: + { + integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==, + } + engines: { node: ">=10" } + + clsx@2.1.1: + resolution: + { + integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==, + } + engines: { node: ">=6" } + + collapse-white-space@2.1.0: + resolution: + { + integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==, + } + + comma-separated-tokens@2.0.3: + resolution: + { + integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==, + } + + commander@11.1.0: + resolution: + { + integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==, + } + engines: { node: ">=16" } + + common-ancestor-path@1.0.1: + resolution: + { + integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==, + } + + cookie-es@1.2.2: + resolution: + { + integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==, + } + + cookie@1.1.1: + resolution: + { + integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==, + } + engines: { node: ">=18" } + + crossws@0.3.5: + resolution: + { + integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==, + } + + css-select@5.2.2: + resolution: + { + integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==, + } + + css-selector-parser@3.3.0: + resolution: + { + integrity: sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==, + } + + css-tree@2.2.1: + resolution: + { + integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==, + } + engines: { node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: ">=7.0.0" } + + css-tree@3.1.0: + resolution: + { + integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==, + } + engines: { node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0 } + + css-what@6.2.2: + resolution: + { + integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==, + } + engines: { node: ">= 6" } + + cssesc@3.0.0: + resolution: + { + integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==, + } + engines: { node: ">=4" } + hasBin: true + + csso@5.0.5: + resolution: + { + integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==, + } + engines: { node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: ">=7.0.0" } + + debug@4.4.3: + resolution: + { + integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==, + } + engines: { node: ">=6.0" } + peerDependencies: + supports-color: "*" + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.3.0: + resolution: + { + integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==, + } + + defu@6.1.4: + resolution: + { + integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==, + } + + dequal@2.0.3: + resolution: + { + integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==, + } + engines: { node: ">=6" } + + destr@2.0.5: + resolution: + { + integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==, + } + + detect-libc@2.1.2: + resolution: + { + integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==, + } + engines: { node: ">=8" } + + deterministic-object-hash@2.0.2: + resolution: + { + integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==, + } + engines: { node: ">=18" } + + devalue@5.6.2: + resolution: + { + integrity: sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==, + } + + devlop@1.1.0: + resolution: + { + integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==, + } + + diff@8.0.3: + resolution: + { + integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==, + } + engines: { node: ">=0.3.1" } + + direction@2.0.1: + resolution: + { + integrity: sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==, + } + hasBin: true + + dlv@1.1.3: + resolution: + { + integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==, + } + + dom-serializer@2.0.0: + resolution: + { + integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==, + } + + domelementtype@2.3.0: + resolution: + { + integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==, + } + + domhandler@5.0.3: + resolution: + { + integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==, + } + engines: { node: ">= 4" } + + domutils@3.2.2: + resolution: + { + integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==, + } + + dset@3.1.4: + resolution: + { + integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==, + } + engines: { node: ">=4" } + + emoji-regex@10.6.0: + resolution: + { + integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==, + } + + emoji-regex@8.0.0: + resolution: + { + integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, + } + + entities@4.5.0: + resolution: + { + integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==, + } + engines: { node: ">=0.12" } + + entities@6.0.1: + resolution: + { + integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==, + } + engines: { node: ">=0.12" } + + es-module-lexer@1.7.0: + resolution: + { + integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==, + } + + esast-util-from-estree@2.0.0: + resolution: + { + integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==, + } + + esast-util-from-js@2.0.1: + resolution: + { + integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==, + } + + esbuild@0.25.12: + resolution: + { + integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==, + } + engines: { node: ">=18" } + hasBin: true + + esbuild@0.27.3: + resolution: + { + integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==, + } + engines: { node: ">=18" } + hasBin: true + + escape-string-regexp@5.0.0: + resolution: + { + integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==, + } + engines: { node: ">=12" } + + estree-util-attach-comments@3.0.0: + resolution: + { + integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==, + } + + estree-util-build-jsx@3.0.1: + resolution: + { + integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==, + } + + estree-util-is-identifier-name@3.0.0: + resolution: + { + integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==, + } + + estree-util-scope@1.0.0: + resolution: + { + integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==, + } + + estree-util-to-js@2.0.0: + resolution: + { + integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==, + } + + estree-util-visit@2.0.0: + resolution: + { + integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==, + } + + estree-walker@2.0.2: + resolution: + { + integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==, + } + + estree-walker@3.0.3: + resolution: + { + integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==, + } + + eventemitter3@5.0.4: + resolution: + { + integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==, + } + + expressive-code@0.41.6: + resolution: + { + integrity: sha512-W/5+IQbrpCIM5KGLjO35wlp1NCwDOOVQb+PAvzEoGkW1xjGM807ZGfBKptNWH6UECvt6qgmLyWolCMYKh7eQmA==, + } + + extend@3.0.2: + resolution: + { + integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==, + } + + fast-deep-equal@3.1.3: + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, + } + + fast-uri@3.1.0: + resolution: + { + integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==, + } + + fdir@6.5.0: + resolution: + { + integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==, + } + engines: { node: ">=12.0.0" } + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + flattie@1.1.1: + resolution: + { + integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==, + } + engines: { node: ">=8" } + + fontace@0.4.1: + resolution: + { + integrity: sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==, + } + + fontkitten@1.0.2: + resolution: + { + integrity: sha512-piJxbLnkD9Xcyi7dWJRnqszEURixe7CrF/efBfbffe2DPyabmuIuqraruY8cXTs19QoM8VJzx47BDRVNXETM7Q==, + } + engines: { node: ">=20" } + + fsevents@2.3.3: + resolution: + { + integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, + } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + os: [darwin] + + get-east-asian-width@1.4.0: + resolution: + { + integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==, + } + engines: { node: ">=18" } + + github-slugger@2.0.0: + resolution: + { + integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==, + } + + h3@1.15.5: + resolution: + { + integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==, + } + + hast-util-embedded@3.0.0: + resolution: + { + integrity: sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==, + } + + hast-util-format@1.1.0: + resolution: + { + integrity: sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==, + } + + hast-util-from-html@2.0.3: + resolution: + { + integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==, + } + + hast-util-from-parse5@8.0.3: + resolution: + { + integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==, + } + + hast-util-has-property@3.0.0: + resolution: + { + integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==, + } + + hast-util-is-body-ok-link@3.0.1: + resolution: + { + integrity: sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==, + } + + hast-util-is-element@3.0.0: + resolution: + { + integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==, + } + + hast-util-minify-whitespace@1.0.1: + resolution: + { + integrity: sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==, + } + + hast-util-parse-selector@4.0.0: + resolution: + { + integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==, + } + + hast-util-phrasing@3.0.1: + resolution: + { + integrity: sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==, + } + + hast-util-raw@9.1.0: + resolution: + { + integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==, + } + + hast-util-select@6.0.4: + resolution: + { + integrity: sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==, + } + + hast-util-to-estree@3.1.3: + resolution: + { + integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==, + } + + hast-util-to-html@9.0.5: + resolution: + { + integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==, + } + + hast-util-to-jsx-runtime@2.3.6: + resolution: + { + integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==, + } + + hast-util-to-parse5@8.0.1: + resolution: + { + integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==, + } + + hast-util-to-string@3.0.1: + resolution: + { + integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==, + } + + hast-util-to-text@4.0.2: + resolution: + { + integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==, + } + + hast-util-whitespace@3.0.0: + resolution: + { + integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==, + } + + hastscript@9.0.1: + resolution: + { + integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==, + } + + html-escaper@3.0.3: + resolution: + { + integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==, + } + + html-void-elements@3.0.0: + resolution: + { + integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==, + } + + html-whitespace-sensitive-tag-names@3.0.1: + resolution: + { + integrity: sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==, + } + + http-cache-semantics@4.2.0: + resolution: + { + integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==, + } + + i18next@23.16.8: + resolution: + { + integrity: sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==, + } + + import-meta-resolve@4.2.0: + resolution: + { + integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==, + } + + inline-style-parser@0.2.7: + resolution: + { + integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==, + } + + iron-webcrypto@1.2.1: + resolution: + { + integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==, + } + + is-alphabetical@2.0.1: + resolution: + { + integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==, + } + + is-alphanumerical@2.0.1: + resolution: + { + integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==, + } + + is-decimal@2.0.1: + resolution: + { + integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==, + } + + is-docker@3.0.0: + resolution: + { + integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + hasBin: true + + is-fullwidth-code-point@3.0.0: + resolution: + { + integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, + } + engines: { node: ">=8" } + + is-hexadecimal@2.0.1: + resolution: + { + integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==, + } + + is-inside-container@1.0.0: + resolution: + { + integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==, + } + engines: { node: ">=14.16" } + hasBin: true + + is-plain-obj@4.1.0: + resolution: + { + integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==, + } + engines: { node: ">=12" } + + is-wsl@3.1.1: + resolution: + { + integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==, + } + engines: { node: ">=16" } + + js-tokens@4.0.0: + resolution: + { + integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, + } + + js-yaml@4.1.1: + resolution: + { + integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==, + } + hasBin: true + + json-schema-traverse@1.0.0: + resolution: + { + integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==, + } + + jsonpointer@5.0.1: + resolution: + { + integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==, + } + engines: { node: ">=0.10.0" } + + kleur@3.0.3: + resolution: + { + integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==, + } + engines: { node: ">=6" } + + klona@2.0.6: + resolution: + { + integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==, + } + engines: { node: ">= 8" } + + leven@3.1.0: + resolution: + { + integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==, + } + engines: { node: ">=6" } + + longest-streak@3.1.0: + resolution: + { + integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==, + } + + lru-cache@11.2.6: + resolution: + { + integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==, + } + engines: { node: 20 || >=22 } + + magic-string@0.30.21: + resolution: + { + integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==, + } + + magicast@0.5.2: + resolution: + { + integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==, + } + + markdown-extensions@2.0.0: + resolution: + { + integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==, + } + engines: { node: ">=16" } + + markdown-table@3.0.4: + resolution: + { + integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==, + } + + mdast-util-definitions@6.0.0: + resolution: + { + integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==, + } + + mdast-util-directive@3.1.0: + resolution: + { + integrity: sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==, + } + + mdast-util-find-and-replace@3.0.2: + resolution: + { + integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==, + } + + mdast-util-from-markdown@2.0.2: + resolution: + { + integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==, + } + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: + { + integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==, + } + + mdast-util-gfm-footnote@2.1.0: + resolution: + { + integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==, + } + + mdast-util-gfm-strikethrough@2.0.0: + resolution: + { + integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==, + } + + mdast-util-gfm-table@2.0.0: + resolution: + { + integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==, + } + + mdast-util-gfm-task-list-item@2.0.0: + resolution: + { + integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==, + } + + mdast-util-gfm@3.1.0: + resolution: + { + integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==, + } + + mdast-util-mdx-expression@2.0.1: + resolution: + { + integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==, + } + + mdast-util-mdx-jsx@3.2.0: + resolution: + { + integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==, + } + + mdast-util-mdx@3.0.0: + resolution: + { + integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==, + } + + mdast-util-mdxjs-esm@2.0.1: + resolution: + { + integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==, + } + + mdast-util-phrasing@4.1.0: + resolution: + { + integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==, + } + + mdast-util-to-hast@13.2.1: + resolution: + { + integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==, + } + + mdast-util-to-markdown@2.1.2: + resolution: + { + integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==, + } + + mdast-util-to-string@4.0.0: + resolution: + { + integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==, + } + + mdn-data@2.0.28: + resolution: + { + integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==, + } + + mdn-data@2.12.2: + resolution: + { + integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==, + } + + micromark-core-commonmark@2.0.3: + resolution: + { + integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==, + } + + micromark-extension-directive@3.0.2: + resolution: + { + integrity: sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==, + } + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: + { + integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==, + } + + micromark-extension-gfm-footnote@2.1.0: + resolution: + { + integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==, + } + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: + { + integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==, + } + + micromark-extension-gfm-table@2.1.1: + resolution: + { + integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==, + } + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: + { + integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==, + } + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: + { + integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==, + } + + micromark-extension-gfm@3.0.0: + resolution: + { + integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==, + } + + micromark-extension-mdx-expression@3.0.1: + resolution: + { + integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==, + } + + micromark-extension-mdx-jsx@3.0.2: + resolution: + { + integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==, + } + + micromark-extension-mdx-md@2.0.0: + resolution: + { + integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==, + } + + micromark-extension-mdxjs-esm@3.0.0: + resolution: + { + integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==, + } + + micromark-extension-mdxjs@3.0.0: + resolution: + { + integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==, + } + + micromark-factory-destination@2.0.1: + resolution: + { + integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==, + } + + micromark-factory-label@2.0.1: + resolution: + { + integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==, + } + + micromark-factory-mdx-expression@2.0.3: + resolution: + { + integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==, + } + + micromark-factory-space@2.0.1: + resolution: + { + integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==, + } + + micromark-factory-title@2.0.1: + resolution: + { + integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==, + } + + micromark-factory-whitespace@2.0.1: + resolution: + { + integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==, + } + + micromark-util-character@2.1.1: + resolution: + { + integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==, + } + + micromark-util-chunked@2.0.1: + resolution: + { + integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==, + } + + micromark-util-classify-character@2.0.1: + resolution: + { + integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==, + } + + micromark-util-combine-extensions@2.0.1: + resolution: + { + integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==, + } + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: + { + integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==, + } + + micromark-util-decode-string@2.0.1: + resolution: + { + integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==, + } + + micromark-util-encode@2.0.1: + resolution: + { + integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==, + } + + micromark-util-events-to-acorn@2.0.3: + resolution: + { + integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==, + } + + micromark-util-html-tag-name@2.0.1: + resolution: + { + integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==, + } + + micromark-util-normalize-identifier@2.0.1: + resolution: + { + integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==, + } + + micromark-util-resolve-all@2.0.1: + resolution: + { + integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==, + } + + micromark-util-sanitize-uri@2.0.1: + resolution: + { + integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==, + } + + micromark-util-subtokenize@2.1.0: + resolution: + { + integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==, + } + + micromark-util-symbol@2.0.1: + resolution: + { + integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==, + } + + micromark-util-types@2.0.2: + resolution: + { + integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==, + } + + micromark@4.0.2: + resolution: + { + integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==, + } + + mrmime@2.0.1: + resolution: + { + integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==, + } + engines: { node: ">=10" } + + ms@2.1.3: + resolution: + { + integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, + } + + nanoid@3.3.11: + resolution: + { + integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==, + } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + hasBin: true + + neotraverse@0.6.18: + resolution: + { + integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==, + } + engines: { node: ">= 10" } + + nlcst-to-string@4.0.0: + resolution: + { + integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==, + } + + node-fetch-native@1.6.7: + resolution: + { + integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==, + } + + node-mock-http@1.0.4: + resolution: + { + integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==, + } + + normalize-path@3.0.0: + resolution: + { + integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, + } + engines: { node: ">=0.10.0" } + + nth-check@2.1.1: + resolution: + { + integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==, + } + + ofetch@1.5.1: + resolution: + { + integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==, + } + + ohash@2.0.11: + resolution: + { + integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==, + } + + oniguruma-parser@0.12.1: + resolution: + { + integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==, + } + + oniguruma-to-es@4.3.4: + resolution: + { + integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==, + } + + openapi-types@12.1.3: + resolution: + { + integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==, + } + + p-limit@6.2.0: + resolution: + { + integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==, + } + engines: { node: ">=18" } + + p-queue@8.1.1: + resolution: + { + integrity: sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==, + } + engines: { node: ">=18" } + + p-timeout@6.1.4: + resolution: + { + integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==, + } + engines: { node: ">=14.16" } + + package-manager-detector@1.6.0: + resolution: + { + integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==, + } + + pagefind@1.4.0: + resolution: + { + integrity: sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==, + } + hasBin: true + + parse-entities@4.0.2: + resolution: + { + integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==, + } + + parse-latin@7.0.0: + resolution: + { + integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==, + } + + parse5@7.3.0: + resolution: + { + integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==, + } + + piccolore@0.1.3: + resolution: + { + integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==, + } + + picocolors@1.1.1: + resolution: + { + integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, + } + + picomatch@2.3.1: + resolution: + { + integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, + } + engines: { node: ">=8.6" } + + picomatch@4.0.3: + resolution: + { + integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==, + } + engines: { node: ">=12" } + + postcss-nested@6.2.0: + resolution: + { + integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==, + } + engines: { node: ">=12.0" } + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: + { + integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==, + } + engines: { node: ">=4" } + + postcss@8.5.6: + resolution: + { + integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==, + } + engines: { node: ^10 || ^12 || >=14 } + + prismjs@1.30.0: + resolution: + { + integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==, + } + engines: { node: ">=6" } + + prompts@2.4.2: + resolution: + { + integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==, + } + engines: { node: ">= 6" } + + property-information@7.1.0: + resolution: + { + integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==, + } + + radix3@1.1.2: + resolution: + { + integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==, + } + + readdirp@5.0.0: + resolution: + { + integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==, + } + engines: { node: ">= 20.19.0" } + + recma-build-jsx@1.0.0: + resolution: + { + integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==, + } + + recma-jsx@1.0.1: + resolution: + { + integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==, + } + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + recma-parse@1.0.0: + resolution: + { + integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==, + } + + recma-stringify@1.0.0: + resolution: + { + integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==, + } + + regex-recursion@6.0.2: + resolution: + { + integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==, + } + + regex-utilities@2.3.0: + resolution: + { + integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==, + } + + regex@6.1.0: + resolution: + { + integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==, + } + + rehype-expressive-code@0.41.6: + resolution: + { + integrity: sha512-aBMX8kxPtjmDSFUdZlAWJkMvsQ4ZMASfee90JWIAV8tweltXLzkWC3q++43ToTelI8ac5iC0B3/S/Cl4Ql1y2g==, + } + + rehype-format@5.0.1: + resolution: + { + integrity: sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==, + } + + rehype-parse@9.0.1: + resolution: + { + integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==, + } + + rehype-raw@7.0.0: + resolution: + { + integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==, + } + + rehype-recma@1.0.0: + resolution: + { + integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==, + } + + rehype-stringify@10.0.1: + resolution: + { + integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==, + } + + rehype@13.0.2: + resolution: + { + integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==, + } + + remark-directive@3.0.1: + resolution: + { + integrity: sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==, + } + + remark-gfm@4.0.1: + resolution: + { + integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==, + } + + remark-mdx@3.1.1: + resolution: + { + integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==, + } + + remark-parse@11.0.0: + resolution: + { + integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==, + } + + remark-rehype@11.1.2: + resolution: + { + integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==, + } + + remark-smartypants@3.0.2: + resolution: + { + integrity: sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==, + } + engines: { node: ">=16.0.0" } + + remark-stringify@11.0.0: + resolution: + { + integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==, + } + + require-from-string@2.0.2: + resolution: + { + integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==, + } + engines: { node: ">=0.10.0" } + + retext-latin@4.0.0: + resolution: + { + integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==, + } + + retext-smartypants@6.2.0: + resolution: + { + integrity: sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==, + } + + retext-stringify@4.0.0: + resolution: + { + integrity: sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==, + } + + retext@9.0.0: + resolution: + { + integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==, + } + + rollup@4.57.1: + resolution: + { + integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==, + } + engines: { node: ">=18.0.0", npm: ">=8.0.0" } + hasBin: true + + sax@1.4.4: + resolution: + { + integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==, + } + engines: { node: ">=11.0.0" } + + semver@7.7.4: + resolution: + { + integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==, + } + engines: { node: ">=10" } + hasBin: true + + sharp@0.34.5: + resolution: + { + integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + + shiki@3.22.0: + resolution: + { + integrity: sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==, + } + + sisteransi@1.0.5: + resolution: + { + integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==, + } + + sitemap@8.0.2: + resolution: + { + integrity: sha512-LwktpJcyZDoa0IL6KT++lQ53pbSrx2c9ge41/SeLTyqy2XUNA6uR4+P9u5IVo5lPeL2arAcOKn1aZAxoYbCKlQ==, + } + engines: { node: ">=14.0.0", npm: ">=6.0.0" } + hasBin: true + + smol-toml@1.6.0: + resolution: + { + integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==, + } + engines: { node: ">= 18" } + + source-map-js@1.2.1: + resolution: + { + integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, + } + engines: { node: ">=0.10.0" } + + source-map@0.7.6: + resolution: + { + integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==, + } + engines: { node: ">= 12" } + + space-separated-tokens@2.0.2: + resolution: + { + integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==, + } + + starlight-openapi@0.22.0: + resolution: + { + integrity: sha512-4H/fywAoTcvKbcv+xBr9LdrjMc5geDOSnydvzpiOxjxkHvI6g+7uPhWNCejnJPyZUd/MC1MI23tvYug85d/WzA==, + } + engines: { node: ">=18.17.1" } + peerDependencies: + "@astrojs/markdown-remark": ">=6.0.1" + "@astrojs/starlight": ">=0.34.0" + astro: ">=5.5.0" + + stream-replace-string@2.0.0: + resolution: + { + integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==, + } + + string-width@4.2.3: + resolution: + { + integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, + } + engines: { node: ">=8" } + + string-width@7.2.0: + resolution: + { + integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==, + } + engines: { node: ">=18" } + + stringify-entities@4.0.4: + resolution: + { + integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==, + } + + strip-ansi@6.0.1: + resolution: + { + integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, + } + engines: { node: ">=8" } + + strip-ansi@7.1.2: + resolution: + { + integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==, + } + engines: { node: ">=12" } + + style-to-js@1.1.21: + resolution: + { + integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==, + } + + style-to-object@1.0.14: + resolution: + { + integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==, + } + + svgo@4.0.0: + resolution: + { + integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==, + } + engines: { node: ">=16" } + hasBin: true + + tiny-inflate@1.0.3: + resolution: + { + integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==, + } + + tinyexec@1.0.2: + resolution: + { + integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==, + } + engines: { node: ">=18" } + + tinyglobby@0.2.15: + resolution: + { + integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==, + } + engines: { node: ">=12.0.0" } + + trim-lines@3.0.1: + resolution: + { + integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==, + } + + trough@2.2.0: + resolution: + { + integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==, + } + + tsconfck@3.1.6: + resolution: + { + integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==, + } + engines: { node: ^18 || >=20 } + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + tslib@2.8.1: + resolution: + { + integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, + } + + type-fest@4.41.0: + resolution: + { + integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==, + } + engines: { node: ">=16" } + + typescript@5.9.3: + resolution: + { + integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==, + } + engines: { node: ">=14.17" } + hasBin: true + + ufo@1.6.3: + resolution: + { + integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==, + } + + ultrahtml@1.6.0: + resolution: + { + integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==, + } + + uncrypto@0.1.3: + resolution: + { + integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==, + } + + unified@11.0.5: + resolution: + { + integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==, + } + + unifont@0.7.4: + resolution: + { + integrity: sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==, + } + + unist-util-find-after@5.0.0: + resolution: + { + integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==, + } + + unist-util-is@6.0.1: + resolution: + { + integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==, + } + + unist-util-modify-children@4.0.0: + resolution: + { + integrity: sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==, + } + + unist-util-position-from-estree@2.0.0: + resolution: + { + integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==, + } + + unist-util-position@5.0.0: + resolution: + { + integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==, + } + + unist-util-remove-position@5.0.0: + resolution: + { + integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==, + } + + unist-util-stringify-position@4.0.0: + resolution: + { + integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==, + } + + unist-util-visit-children@3.0.0: + resolution: + { + integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==, + } + + unist-util-visit-parents@6.0.2: + resolution: + { + integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==, + } + + unist-util-visit@5.1.0: + resolution: + { + integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==, + } + + unstorage@1.17.4: + resolution: + { + integrity: sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==, + } + peerDependencies: + "@azure/app-configuration": ^1.8.0 + "@azure/cosmos": ^4.2.0 + "@azure/data-tables": ^13.3.0 + "@azure/identity": ^4.6.0 + "@azure/keyvault-secrets": ^4.9.0 + "@azure/storage-blob": ^12.26.0 + "@capacitor/preferences": ^6 || ^7 || ^8 + "@deno/kv": ">=0.9.0" + "@netlify/blobs": ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + "@planetscale/database": ^1.19.0 + "@upstash/redis": ^1.34.3 + "@vercel/blob": ">=0.27.1" + "@vercel/functions": ^2.2.12 || ^3.0.0 + "@vercel/kv": ^1 || ^2 || ^3 + aws4fetch: ^1.0.20 + db0: ">=0.2.1" + idb-keyval: ^6.2.1 + ioredis: ^5.4.2 + uploadthing: ^7.4.4 + peerDependenciesMeta: + "@azure/app-configuration": + optional: true + "@azure/cosmos": + optional: true + "@azure/data-tables": + optional: true + "@azure/identity": + optional: true + "@azure/keyvault-secrets": + optional: true + "@azure/storage-blob": + optional: true + "@capacitor/preferences": + optional: true + "@deno/kv": + optional: true + "@netlify/blobs": + optional: true + "@planetscale/database": + optional: true + "@upstash/redis": + optional: true + "@vercel/blob": + optional: true + "@vercel/functions": + optional: true + "@vercel/kv": + optional: true + aws4fetch: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + uploadthing: + optional: true + + url-template@3.1.1: + resolution: + { + integrity: sha512-4oszoaEKE/mQOtAmdMWqIRHmkxWkUZMnXFnjQ5i01CuRSK3uluxcH1MRVVVWmhlnzT1SCDfKxxficm2G37qzCA==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + util-deprecate@1.0.2: + resolution: + { + integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, + } + + vfile-location@5.0.3: + resolution: + { + integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==, + } + + vfile-message@4.0.3: + resolution: + { + integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==, + } + + vfile@6.0.3: + resolution: + { + integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==, + } + + vite@6.4.1: + resolution: + { + integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==, + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + hasBin: true + peerDependencies: + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: ">=1.21.0" + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.1.1: + resolution: + { + integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==, + } + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 + peerDependenciesMeta: + vite: + optional: true + + web-namespaces@2.0.1: + resolution: + { + integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==, + } + + which-pm-runs@1.1.0: + resolution: + { + integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==, + } + engines: { node: ">=4" } + + widest-line@5.0.0: + resolution: + { + integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==, + } + engines: { node: ">=18" } + + wrap-ansi@9.0.2: + resolution: + { + integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==, + } + engines: { node: ">=18" } + + xxhash-wasm@1.1.0: + resolution: + { + integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==, + } + + yargs-parser@21.1.1: + resolution: + { + integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==, + } + engines: { node: ">=12" } + + yocto-queue@1.2.2: + resolution: + { + integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==, + } + engines: { node: ">=12.20" } + + yocto-spinner@0.2.3: + resolution: + { + integrity: sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==, + } + engines: { node: ">=18.19" } + + yoctocolors@2.1.2: + resolution: + { + integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==, + } + engines: { node: ">=18" } + + zod-to-json-schema@3.25.1: + resolution: + { + integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==, + } + peerDependencies: + zod: ^3.25 || ^4 + + zod-to-ts@1.2.0: + resolution: + { + integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==, + } + peerDependencies: + typescript: ^4.9.4 || ^5.0.2 + zod: ^3 + + zod@3.25.76: + resolution: + { + integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==, + } + + zwitch@2.0.4: + resolution: + { + integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==, + } + +snapshots: + "@apidevtools/json-schema-ref-parser@13.0.5": + dependencies: + "@types/json-schema": 7.0.15 + js-yaml: 4.1.1 + + "@astrojs/compiler@2.13.1": {} + + "@astrojs/internal-helpers@0.7.5": {} + + "@astrojs/markdown-remark@6.3.10": + dependencies: + "@astrojs/internal-helpers": 0.7.5 + "@astrojs/prism": 3.3.0 + github-slugger: 2.0.0 + hast-util-from-html: 2.0.3 + hast-util-to-text: 4.0.2 + import-meta-resolve: 4.2.0 + js-yaml: 4.1.1 + mdast-util-definitions: 6.0.0 + rehype-raw: 7.0.0 + rehype-stringify: 10.0.1 + remark-gfm: 4.0.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + remark-smartypants: 3.0.2 + shiki: 3.22.0 + smol-toml: 1.6.0 + unified: 11.0.5 + unist-util-remove-position: 5.0.0 + unist-util-visit: 5.1.0 + unist-util-visit-parents: 6.0.2 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + "@astrojs/mdx@4.3.13(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3))": + dependencies: + "@astrojs/markdown-remark": 6.3.10 + "@mdx-js/mdx": 3.1.1 + acorn: 8.15.0 + astro: 5.17.2(rollup@4.57.1)(typescript@5.9.3) + es-module-lexer: 1.7.0 + estree-util-visit: 2.0.0 + hast-util-to-html: 9.0.5 + piccolore: 0.1.3 + rehype-raw: 7.0.0 + remark-gfm: 4.0.1 + remark-smartypants: 3.0.2 + source-map: 0.7.6 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + "@astrojs/prism@3.3.0": + dependencies: + prismjs: 1.30.0 + + "@astrojs/sitemap@3.7.0": + dependencies: + sitemap: 8.0.2 + stream-replace-string: 2.0.0 + zod: 3.25.76 + + "@astrojs/starlight@0.37.6(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3))": + dependencies: + "@astrojs/markdown-remark": 6.3.10 + "@astrojs/mdx": 4.3.13(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3)) + "@astrojs/sitemap": 3.7.0 + "@pagefind/default-ui": 1.4.0 + "@types/hast": 3.0.4 + "@types/js-yaml": 4.0.9 + "@types/mdast": 4.0.4 + astro: 5.17.2(rollup@4.57.1)(typescript@5.9.3) + astro-expressive-code: 0.41.6(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3)) + bcp-47: 2.1.0 + hast-util-from-html: 2.0.3 + hast-util-select: 6.0.4 + hast-util-to-string: 3.0.1 + hastscript: 9.0.1 + i18next: 23.16.8 + js-yaml: 4.1.1 + klona: 2.0.6 + magic-string: 0.30.21 + mdast-util-directive: 3.1.0 + mdast-util-to-markdown: 2.1.2 + mdast-util-to-string: 4.0.0 + pagefind: 1.4.0 + rehype: 13.0.2 + rehype-format: 5.0.1 + remark-directive: 3.0.1 + ultrahtml: 1.6.0 + unified: 11.0.5 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + "@astrojs/telemetry@3.3.0": + dependencies: + ci-info: 4.4.0 + debug: 4.4.3 + dlv: 1.1.3 + dset: 3.1.4 + is-docker: 3.0.0 + is-wsl: 3.1.1 + which-pm-runs: 1.1.0 + transitivePeerDependencies: + - supports-color + + "@babel/code-frame@7.29.0": + dependencies: + "@babel/helper-validator-identifier": 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + "@babel/helper-string-parser@7.27.1": {} + + "@babel/helper-validator-identifier@7.28.5": {} + + "@babel/parser@7.29.0": + dependencies: + "@babel/types": 7.29.0 + + "@babel/runtime@7.28.6": {} + + "@babel/types@7.29.0": + dependencies: + "@babel/helper-string-parser": 7.27.1 + "@babel/helper-validator-identifier": 7.28.5 + + "@capsizecss/unpack@4.0.0": + dependencies: + fontkitten: 1.0.2 + + "@ctrl/tinycolor@4.2.0": {} + + "@emnapi/runtime@1.8.1": + dependencies: + tslib: 2.8.1 + optional: true + + "@esbuild/aix-ppc64@0.25.12": + optional: true + + "@esbuild/aix-ppc64@0.27.3": + optional: true + + "@esbuild/android-arm64@0.25.12": + optional: true + + "@esbuild/android-arm64@0.27.3": + optional: true + + "@esbuild/android-arm@0.25.12": + optional: true + + "@esbuild/android-arm@0.27.3": + optional: true + + "@esbuild/android-x64@0.25.12": + optional: true + + "@esbuild/android-x64@0.27.3": + optional: true + + "@esbuild/darwin-arm64@0.25.12": + optional: true + + "@esbuild/darwin-arm64@0.27.3": + optional: true + + "@esbuild/darwin-x64@0.25.12": + optional: true + + "@esbuild/darwin-x64@0.27.3": + optional: true + + "@esbuild/freebsd-arm64@0.25.12": + optional: true + + "@esbuild/freebsd-arm64@0.27.3": + optional: true + + "@esbuild/freebsd-x64@0.25.12": + optional: true + + "@esbuild/freebsd-x64@0.27.3": + optional: true + + "@esbuild/linux-arm64@0.25.12": + optional: true + + "@esbuild/linux-arm64@0.27.3": + optional: true + + "@esbuild/linux-arm@0.25.12": + optional: true + + "@esbuild/linux-arm@0.27.3": + optional: true + + "@esbuild/linux-ia32@0.25.12": + optional: true + + "@esbuild/linux-ia32@0.27.3": + optional: true + + "@esbuild/linux-loong64@0.25.12": + optional: true + + "@esbuild/linux-loong64@0.27.3": + optional: true + + "@esbuild/linux-mips64el@0.25.12": + optional: true + + "@esbuild/linux-mips64el@0.27.3": + optional: true + + "@esbuild/linux-ppc64@0.25.12": + optional: true + + "@esbuild/linux-ppc64@0.27.3": + optional: true + + "@esbuild/linux-riscv64@0.25.12": + optional: true + + "@esbuild/linux-riscv64@0.27.3": + optional: true + + "@esbuild/linux-s390x@0.25.12": + optional: true + + "@esbuild/linux-s390x@0.27.3": + optional: true + + "@esbuild/linux-x64@0.25.12": + optional: true + + "@esbuild/linux-x64@0.27.3": + optional: true + + "@esbuild/netbsd-arm64@0.25.12": + optional: true + + "@esbuild/netbsd-arm64@0.27.3": + optional: true + + "@esbuild/netbsd-x64@0.25.12": + optional: true + + "@esbuild/netbsd-x64@0.27.3": + optional: true + + "@esbuild/openbsd-arm64@0.25.12": + optional: true + + "@esbuild/openbsd-arm64@0.27.3": + optional: true + + "@esbuild/openbsd-x64@0.25.12": + optional: true + + "@esbuild/openbsd-x64@0.27.3": + optional: true + + "@esbuild/openharmony-arm64@0.25.12": + optional: true + + "@esbuild/openharmony-arm64@0.27.3": + optional: true + + "@esbuild/sunos-x64@0.25.12": + optional: true + + "@esbuild/sunos-x64@0.27.3": + optional: true + + "@esbuild/win32-arm64@0.25.12": + optional: true + + "@esbuild/win32-arm64@0.27.3": + optional: true + + "@esbuild/win32-ia32@0.25.12": + optional: true + + "@esbuild/win32-ia32@0.27.3": + optional: true + + "@esbuild/win32-x64@0.25.12": + optional: true + + "@esbuild/win32-x64@0.27.3": + optional: true + + "@expressive-code/core@0.41.6": + dependencies: + "@ctrl/tinycolor": 4.2.0 + hast-util-select: 6.0.4 + hast-util-to-html: 9.0.5 + hast-util-to-text: 4.0.2 + hastscript: 9.0.1 + postcss: 8.5.6 + postcss-nested: 6.2.0(postcss@8.5.6) + unist-util-visit: 5.1.0 + unist-util-visit-parents: 6.0.2 + + "@expressive-code/plugin-frames@0.41.6": + dependencies: + "@expressive-code/core": 0.41.6 + + "@expressive-code/plugin-shiki@0.41.6": + dependencies: + "@expressive-code/core": 0.41.6 + shiki: 3.22.0 + + "@expressive-code/plugin-text-markers@0.41.6": + dependencies: + "@expressive-code/core": 0.41.6 + + "@fontsource/inter@5.2.8": {} + + "@fontsource/rubik@5.2.8": {} + + "@humanwhocodes/momoa@2.0.4": {} + + "@img/colour@1.0.0": {} + + "@img/sharp-darwin-arm64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-darwin-arm64": 1.2.4 + optional: true + + "@img/sharp-darwin-x64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-darwin-x64": 1.2.4 + optional: true + + "@img/sharp-libvips-darwin-arm64@1.2.4": + optional: true + + "@img/sharp-libvips-darwin-x64@1.2.4": + optional: true + + "@img/sharp-libvips-linux-arm64@1.2.4": + optional: true + + "@img/sharp-libvips-linux-arm@1.2.4": + optional: true + + "@img/sharp-libvips-linux-ppc64@1.2.4": + optional: true + + "@img/sharp-libvips-linux-riscv64@1.2.4": + optional: true + + "@img/sharp-libvips-linux-s390x@1.2.4": + optional: true + + "@img/sharp-libvips-linux-x64@1.2.4": + optional: true + + "@img/sharp-libvips-linuxmusl-arm64@1.2.4": + optional: true + + "@img/sharp-libvips-linuxmusl-x64@1.2.4": + optional: true + + "@img/sharp-linux-arm64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-arm64": 1.2.4 + optional: true + + "@img/sharp-linux-arm@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-arm": 1.2.4 + optional: true + + "@img/sharp-linux-ppc64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-ppc64": 1.2.4 + optional: true + + "@img/sharp-linux-riscv64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-riscv64": 1.2.4 + optional: true + + "@img/sharp-linux-s390x@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-s390x": 1.2.4 + optional: true + + "@img/sharp-linux-x64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-x64": 1.2.4 + optional: true + + "@img/sharp-linuxmusl-arm64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64": 1.2.4 + optional: true + + "@img/sharp-linuxmusl-x64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64": 1.2.4 + optional: true + + "@img/sharp-wasm32@0.34.5": + dependencies: + "@emnapi/runtime": 1.8.1 + optional: true + + "@img/sharp-win32-arm64@0.34.5": + optional: true + + "@img/sharp-win32-ia32@0.34.5": + optional: true + + "@img/sharp-win32-x64@0.34.5": + optional: true + + "@jridgewell/sourcemap-codec@1.5.5": {} + + "@mdx-js/mdx@3.1.1": + dependencies: + "@types/estree": 1.0.8 + "@types/estree-jsx": 1.0.5 + "@types/hast": 3.0.4 + "@types/mdx": 2.0.13 + acorn: 8.15.0 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.1(acorn@8.15.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + source-map: 0.7.6 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + "@oslojs/encoding@1.1.0": {} + + "@pagefind/darwin-arm64@1.4.0": + optional: true + + "@pagefind/darwin-x64@1.4.0": + optional: true + + "@pagefind/default-ui@1.4.0": {} + + "@pagefind/freebsd-x64@1.4.0": + optional: true + + "@pagefind/linux-arm64@1.4.0": + optional: true + + "@pagefind/linux-x64@1.4.0": + optional: true + + "@pagefind/windows-x64@1.4.0": + optional: true + + "@readme/better-ajv-errors@2.4.0(ajv@8.18.0)": + dependencies: + "@babel/code-frame": 7.29.0 + "@babel/runtime": 7.28.6 + "@humanwhocodes/momoa": 2.0.4 + ajv: 8.18.0 + jsonpointer: 5.0.1 + leven: 3.1.0 + picocolors: 1.1.1 + + "@readme/openapi-parser@4.1.2(openapi-types@12.1.3)": + dependencies: + "@apidevtools/json-schema-ref-parser": 13.0.5 + "@readme/better-ajv-errors": 2.4.0(ajv@8.18.0) + "@readme/openapi-schemas": 3.1.0 + "@types/json-schema": 7.0.15 + ajv: 8.18.0 + ajv-draft-04: 1.0.0(ajv@8.18.0) + openapi-types: 12.1.3 + + "@readme/openapi-schemas@3.1.0": {} + + "@rollup/pluginutils@5.3.0(rollup@4.57.1)": + dependencies: + "@types/estree": 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.57.1 + + "@rollup/rollup-android-arm-eabi@4.57.1": + optional: true + + "@rollup/rollup-android-arm64@4.57.1": + optional: true + + "@rollup/rollup-darwin-arm64@4.57.1": + optional: true + + "@rollup/rollup-darwin-x64@4.57.1": + optional: true + + "@rollup/rollup-freebsd-arm64@4.57.1": + optional: true + + "@rollup/rollup-freebsd-x64@4.57.1": + optional: true + + "@rollup/rollup-linux-arm-gnueabihf@4.57.1": + optional: true + + "@rollup/rollup-linux-arm-musleabihf@4.57.1": + optional: true + + "@rollup/rollup-linux-arm64-gnu@4.57.1": + optional: true + + "@rollup/rollup-linux-arm64-musl@4.57.1": + optional: true + + "@rollup/rollup-linux-loong64-gnu@4.57.1": + optional: true + + "@rollup/rollup-linux-loong64-musl@4.57.1": + optional: true + + "@rollup/rollup-linux-ppc64-gnu@4.57.1": + optional: true + + "@rollup/rollup-linux-ppc64-musl@4.57.1": + optional: true + + "@rollup/rollup-linux-riscv64-gnu@4.57.1": + optional: true + + "@rollup/rollup-linux-riscv64-musl@4.57.1": + optional: true + + "@rollup/rollup-linux-s390x-gnu@4.57.1": + optional: true + + "@rollup/rollup-linux-x64-gnu@4.57.1": + optional: true + + "@rollup/rollup-linux-x64-musl@4.57.1": + optional: true + + "@rollup/rollup-openbsd-x64@4.57.1": + optional: true + + "@rollup/rollup-openharmony-arm64@4.57.1": + optional: true + + "@rollup/rollup-win32-arm64-msvc@4.57.1": + optional: true + + "@rollup/rollup-win32-ia32-msvc@4.57.1": + optional: true + + "@rollup/rollup-win32-x64-gnu@4.57.1": + optional: true + + "@rollup/rollup-win32-x64-msvc@4.57.1": + optional: true + + "@shikijs/core@3.22.0": + dependencies: + "@shikijs/types": 3.22.0 + "@shikijs/vscode-textmate": 10.0.2 + "@types/hast": 3.0.4 + hast-util-to-html: 9.0.5 + + "@shikijs/engine-javascript@3.22.0": + dependencies: + "@shikijs/types": 3.22.0 + "@shikijs/vscode-textmate": 10.0.2 + oniguruma-to-es: 4.3.4 + + "@shikijs/engine-oniguruma@3.22.0": + dependencies: + "@shikijs/types": 3.22.0 + "@shikijs/vscode-textmate": 10.0.2 + + "@shikijs/langs@3.22.0": + dependencies: + "@shikijs/types": 3.22.0 + + "@shikijs/themes@3.22.0": + dependencies: + "@shikijs/types": 3.22.0 + + "@shikijs/types@3.22.0": + dependencies: + "@shikijs/vscode-textmate": 10.0.2 + "@types/hast": 3.0.4 + + "@shikijs/vscode-textmate@10.0.2": {} + + "@types/debug@4.1.12": + dependencies: + "@types/ms": 2.1.0 + + "@types/estree-jsx@1.0.5": + dependencies: + "@types/estree": 1.0.8 + + "@types/estree@1.0.8": {} + + "@types/hast@3.0.4": + dependencies: + "@types/unist": 3.0.3 + + "@types/js-yaml@4.0.9": {} + + "@types/json-schema@7.0.15": {} + + "@types/mdast@4.0.4": + dependencies: + "@types/unist": 3.0.3 + + "@types/mdx@2.0.13": {} + + "@types/ms@2.1.0": {} + + "@types/nlcst@2.0.3": + dependencies: + "@types/unist": 3.0.3 + + "@types/node@17.0.45": {} + + "@types/sax@1.2.7": + dependencies: + "@types/node": 17.0.45 + + "@types/unist@2.0.11": {} + + "@types/unist@3.0.3": {} + + "@ungap/structured-clone@1.3.0": {} + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv-draft-04@1.0.0(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@6.2.3: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + array-iterate@2.0.1: {} + + astring@1.9.0: {} + + astro-expressive-code@0.41.6(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3)): + dependencies: + astro: 5.17.2(rollup@4.57.1)(typescript@5.9.3) + rehype-expressive-code: 0.41.6 + + astro@5.17.2(rollup@4.57.1)(typescript@5.9.3): + dependencies: + "@astrojs/compiler": 2.13.1 + "@astrojs/internal-helpers": 0.7.5 + "@astrojs/markdown-remark": 6.3.10 + "@astrojs/telemetry": 3.3.0 + "@capsizecss/unpack": 4.0.0 + "@oslojs/encoding": 1.1.0 + "@rollup/pluginutils": 5.3.0(rollup@4.57.1) + acorn: 8.15.0 + aria-query: 5.3.2 + axobject-query: 4.1.0 + boxen: 8.0.1 + ci-info: 4.4.0 + clsx: 2.1.1 + common-ancestor-path: 1.0.1 + cookie: 1.1.1 + cssesc: 3.0.0 + debug: 4.4.3 + deterministic-object-hash: 2.0.2 + devalue: 5.6.2 + diff: 8.0.3 + dlv: 1.1.3 + dset: 3.1.4 + es-module-lexer: 1.7.0 + esbuild: 0.27.3 + estree-walker: 3.0.3 + flattie: 1.1.1 + fontace: 0.4.1 + github-slugger: 2.0.0 + html-escaper: 3.0.3 + http-cache-semantics: 4.2.0 + import-meta-resolve: 4.2.0 + js-yaml: 4.1.1 + magic-string: 0.30.21 + magicast: 0.5.2 + mrmime: 2.0.1 + neotraverse: 0.6.18 + p-limit: 6.2.0 + p-queue: 8.1.1 + package-manager-detector: 1.6.0 + piccolore: 0.1.3 + picomatch: 4.0.3 + prompts: 2.4.2 + rehype: 13.0.2 + semver: 7.7.4 + shiki: 3.22.0 + smol-toml: 1.6.0 + svgo: 4.0.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tsconfck: 3.1.6(typescript@5.9.3) + ultrahtml: 1.6.0 + unifont: 0.7.4 + unist-util-visit: 5.1.0 + unstorage: 1.17.4 + vfile: 6.0.3 + vite: 6.4.1 + vitefu: 1.1.1(vite@6.4.1) + xxhash-wasm: 1.1.0 + yargs-parser: 21.1.1 + yocto-spinner: 0.2.3 + zod: 3.25.76 + zod-to-json-schema: 3.25.1(zod@3.25.76) + zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76) + optionalDependencies: + sharp: 0.34.5 + transitivePeerDependencies: + - "@azure/app-configuration" + - "@azure/cosmos" + - "@azure/data-tables" + - "@azure/identity" + - "@azure/keyvault-secrets" + - "@azure/storage-blob" + - "@capacitor/preferences" + - "@deno/kv" + - "@netlify/blobs" + - "@planetscale/database" + - "@types/node" + - "@upstash/redis" + - "@vercel/blob" + - "@vercel/functions" + - "@vercel/kv" + - aws4fetch + - db0 + - idb-keyval + - ioredis + - jiti + - less + - lightningcss + - rollup + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - typescript + - uploadthing + - yaml + + axobject-query@4.1.0: {} + + bail@2.0.2: {} + + base-64@1.0.0: {} + + bcp-47-match@2.0.3: {} + + bcp-47@2.1.0: + dependencies: + is-alphabetical: 2.0.1 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + + boolbase@1.0.0: {} + + boxen@8.0.1: + dependencies: + ansi-align: 3.0.1 + camelcase: 8.0.0 + chalk: 5.6.2 + cli-boxes: 3.0.0 + string-width: 7.2.0 + type-fest: 4.41.0 + widest-line: 5.0.0 + wrap-ansi: 9.0.2 + + camelcase@8.0.0: {} + + ccount@2.0.1: {} + + chalk@5.6.2: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + ci-info@4.4.0: {} + + cli-boxes@3.0.0: {} + + clsx@2.1.1: {} + + collapse-white-space@2.1.0: {} + + comma-separated-tokens@2.0.3: {} + + commander@11.1.0: {} + + common-ancestor-path@1.0.1: {} + + cookie-es@1.2.2: {} + + cookie@1.1.1: {} + + crossws@0.3.5: + dependencies: + uncrypto: 0.1.3 + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-selector-parser@3.3.0: {} + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.1 + + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + css-what@6.2.2: {} + + cssesc@3.0.0: {} + + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + defu@6.1.4: {} + + dequal@2.0.3: {} + + destr@2.0.5: {} + + detect-libc@2.1.2: {} + + deterministic-object-hash@2.0.2: + dependencies: + base-64: 1.0.0 + + devalue@5.6.2: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + diff@8.0.3: {} + + direction@2.0.1: {} + + dlv@1.1.3: {} + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dset@3.1.4: {} + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + entities@4.5.0: {} + + entities@6.0.1: {} + + es-module-lexer@1.7.0: {} + + esast-util-from-estree@2.0.0: + dependencies: + "@types/estree-jsx": 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + "@types/estree-jsx": 1.0.5 + acorn: 8.15.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.3 + + esbuild@0.25.12: + optionalDependencies: + "@esbuild/aix-ppc64": 0.25.12 + "@esbuild/android-arm": 0.25.12 + "@esbuild/android-arm64": 0.25.12 + "@esbuild/android-x64": 0.25.12 + "@esbuild/darwin-arm64": 0.25.12 + "@esbuild/darwin-x64": 0.25.12 + "@esbuild/freebsd-arm64": 0.25.12 + "@esbuild/freebsd-x64": 0.25.12 + "@esbuild/linux-arm": 0.25.12 + "@esbuild/linux-arm64": 0.25.12 + "@esbuild/linux-ia32": 0.25.12 + "@esbuild/linux-loong64": 0.25.12 + "@esbuild/linux-mips64el": 0.25.12 + "@esbuild/linux-ppc64": 0.25.12 + "@esbuild/linux-riscv64": 0.25.12 + "@esbuild/linux-s390x": 0.25.12 + "@esbuild/linux-x64": 0.25.12 + "@esbuild/netbsd-arm64": 0.25.12 + "@esbuild/netbsd-x64": 0.25.12 + "@esbuild/openbsd-arm64": 0.25.12 + "@esbuild/openbsd-x64": 0.25.12 + "@esbuild/openharmony-arm64": 0.25.12 + "@esbuild/sunos-x64": 0.25.12 + "@esbuild/win32-arm64": 0.25.12 + "@esbuild/win32-ia32": 0.25.12 + "@esbuild/win32-x64": 0.25.12 + + esbuild@0.27.3: + optionalDependencies: + "@esbuild/aix-ppc64": 0.27.3 + "@esbuild/android-arm": 0.27.3 + "@esbuild/android-arm64": 0.27.3 + "@esbuild/android-x64": 0.27.3 + "@esbuild/darwin-arm64": 0.27.3 + "@esbuild/darwin-x64": 0.27.3 + "@esbuild/freebsd-arm64": 0.27.3 + "@esbuild/freebsd-x64": 0.27.3 + "@esbuild/linux-arm": 0.27.3 + "@esbuild/linux-arm64": 0.27.3 + "@esbuild/linux-ia32": 0.27.3 + "@esbuild/linux-loong64": 0.27.3 + "@esbuild/linux-mips64el": 0.27.3 + "@esbuild/linux-ppc64": 0.27.3 + "@esbuild/linux-riscv64": 0.27.3 + "@esbuild/linux-s390x": 0.27.3 + "@esbuild/linux-x64": 0.27.3 + "@esbuild/netbsd-arm64": 0.27.3 + "@esbuild/netbsd-x64": 0.27.3 + "@esbuild/openbsd-arm64": 0.27.3 + "@esbuild/openbsd-x64": 0.27.3 + "@esbuild/openharmony-arm64": 0.27.3 + "@esbuild/sunos-x64": 0.27.3 + "@esbuild/win32-arm64": 0.27.3 + "@esbuild/win32-ia32": 0.27.3 + "@esbuild/win32-x64": 0.27.3 + + escape-string-regexp@5.0.0: {} + + estree-util-attach-comments@3.0.0: + dependencies: + "@types/estree": 1.0.8 + + estree-util-build-jsx@3.0.1: + dependencies: + "@types/estree-jsx": 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-scope@1.0.0: + dependencies: + "@types/estree": 1.0.8 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + "@types/estree-jsx": 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-visit@2.0.0: + dependencies: + "@types/estree-jsx": 1.0.5 + "@types/unist": 3.0.3 + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + "@types/estree": 1.0.8 + + eventemitter3@5.0.4: {} + + expressive-code@0.41.6: + dependencies: + "@expressive-code/core": 0.41.6 + "@expressive-code/plugin-frames": 0.41.6 + "@expressive-code/plugin-shiki": 0.41.6 + "@expressive-code/plugin-text-markers": 0.41.6 + + extend@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.0: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + flattie@1.1.1: {} + + fontace@0.4.1: + dependencies: + fontkitten: 1.0.2 + + fontkitten@1.0.2: + dependencies: + tiny-inflate: 1.0.3 + + fsevents@2.3.3: + optional: true + + get-east-asian-width@1.4.0: {} + + github-slugger@2.0.0: {} + + h3@1.15.5: + dependencies: + cookie-es: 1.2.2 + crossws: 0.3.5 + defu: 6.1.4 + destr: 2.0.5 + iron-webcrypto: 1.2.1 + node-mock-http: 1.0.4 + radix3: 1.1.2 + ufo: 1.6.3 + uncrypto: 0.1.3 + + hast-util-embedded@3.0.0: + dependencies: + "@types/hast": 3.0.4 + hast-util-is-element: 3.0.0 + + hast-util-format@1.1.0: + dependencies: + "@types/hast": 3.0.4 + hast-util-embedded: 3.0.0 + hast-util-minify-whitespace: 1.0.1 + hast-util-phrasing: 3.0.1 + hast-util-whitespace: 3.0.0 + html-whitespace-sensitive-tag-names: 3.0.1 + unist-util-visit-parents: 6.0.2 + + hast-util-from-html@2.0.3: + dependencies: + "@types/hast": 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + "@types/hast": 3.0.4 + "@types/unist": 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-has-property@3.0.0: + dependencies: + "@types/hast": 3.0.4 + + hast-util-is-body-ok-link@3.0.1: + dependencies: + "@types/hast": 3.0.4 + + hast-util-is-element@3.0.0: + dependencies: + "@types/hast": 3.0.4 + + hast-util-minify-whitespace@1.0.1: + dependencies: + "@types/hast": 3.0.4 + hast-util-embedded: 3.0.0 + hast-util-is-element: 3.0.0 + hast-util-whitespace: 3.0.0 + unist-util-is: 6.0.1 + + hast-util-parse-selector@4.0.0: + dependencies: + "@types/hast": 3.0.4 + + hast-util-phrasing@3.0.1: + dependencies: + "@types/hast": 3.0.4 + hast-util-embedded: 3.0.0 + hast-util-has-property: 3.0.0 + hast-util-is-body-ok-link: 3.0.1 + hast-util-is-element: 3.0.0 + + hast-util-raw@9.1.0: + dependencies: + "@types/hast": 3.0.4 + "@types/unist": 3.0.3 + "@ungap/structured-clone": 1.3.0 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.1 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-select@6.0.4: + dependencies: + "@types/hast": 3.0.4 + "@types/unist": 3.0.3 + bcp-47-match: 2.0.3 + comma-separated-tokens: 2.0.3 + css-selector-parser: 3.3.0 + devlop: 1.1.0 + direction: 2.0.1 + hast-util-has-property: 3.0.0 + hast-util-to-string: 3.0.1 + hast-util-whitespace: 3.0.0 + nth-check: 2.1.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + hast-util-to-estree@3.1.3: + dependencies: + "@types/estree": 1.0.8 + "@types/estree-jsx": 1.0.5 + "@types/hast": 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-html@9.0.5: + dependencies: + "@types/hast": 3.0.4 + "@types/unist": 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + "@types/estree": 1.0.8 + "@types/hast": 3.0.4 + "@types/unist": 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-to-parse5@8.0.1: + dependencies: + "@types/hast": 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-string@3.0.1: + dependencies: + "@types/hast": 3.0.4 + + hast-util-to-text@4.0.2: + dependencies: + "@types/hast": 3.0.4 + "@types/unist": 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@3.0.0: + dependencies: + "@types/hast": 3.0.4 + + hastscript@9.0.1: + dependencies: + "@types/hast": 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + + html-escaper@3.0.3: {} + + html-void-elements@3.0.0: {} + + html-whitespace-sensitive-tag-names@3.0.1: {} + + http-cache-semantics@4.2.0: {} + + i18next@23.16.8: + dependencies: + "@babel/runtime": 7.28.6 + + import-meta-resolve@4.2.0: {} + + inline-style-parser@0.2.7: {} + + iron-webcrypto@1.2.1: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-decimal@2.0.1: {} + + is-docker@3.0.0: {} + + is-fullwidth-code-point@3.0.0: {} + + is-hexadecimal@2.0.1: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-plain-obj@4.1.0: {} + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-schema-traverse@1.0.0: {} + + jsonpointer@5.0.1: {} + + kleur@3.0.3: {} + + klona@2.0.6: {} + + leven@3.1.0: {} + + longest-streak@3.1.0: {} + + lru-cache@11.2.6: {} + + magic-string@0.30.21: + dependencies: + "@jridgewell/sourcemap-codec": 1.5.5 + + magicast@0.5.2: + dependencies: + "@babel/parser": 7.29.0 + "@babel/types": 7.29.0 + source-map-js: 1.2.1 + + markdown-extensions@2.0.0: {} + + markdown-table@3.0.4: {} + + mdast-util-definitions@6.0.0: + dependencies: + "@types/mdast": 4.0.4 + "@types/unist": 3.0.3 + unist-util-visit: 5.1.0 + + mdast-util-directive@3.1.0: + dependencies: + "@types/mdast": 4.0.4 + "@types/unist": 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-visit-parents: 6.0.2 + transitivePeerDependencies: + - supports-color + + mdast-util-find-and-replace@3.0.2: + dependencies: + "@types/mdast": 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.2: + dependencies: + "@types/mdast": 4.0.4 + "@types/unist": 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + "@types/mdast": 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + "@types/mdast": 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + "@types/mdast": 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + "@types/mdast": 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + "@types/mdast": 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + "@types/estree-jsx": 1.0.5 + "@types/hast": 3.0.4 + "@types/mdast": 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + "@types/estree-jsx": 1.0.5 + "@types/hast": 3.0.4 + "@types/mdast": 4.0.4 + "@types/unist": 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + "@types/estree-jsx": 1.0.5 + "@types/hast": 3.0.4 + "@types/mdast": 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + "@types/mdast": 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + "@types/hast": 3.0.4 + "@types/mdast": 4.0.4 + "@ungap/structured-clone": 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + "@types/mdast": 4.0.4 + "@types/unist": 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + "@types/mdast": 4.0.4 + + mdn-data@2.0.28: {} + + mdn-data@2.12.2: {} + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-directive@3.0.2: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + parse-entities: 4.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-expression@3.0.1: + dependencies: + "@types/estree": 1.0.8 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + "@types/estree": 1.0.8 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + "@types/estree": 1.0.8 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-mdx-expression@2.0.3: + dependencies: + "@types/estree": 1.0.8 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@2.0.3: + dependencies: + "@types/estree": 1.0.8 + "@types/unist": 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + "@types/debug": 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + neotraverse@0.6.18: {} + + nlcst-to-string@4.0.0: + dependencies: + "@types/nlcst": 2.0.3 + + node-fetch-native@1.6.7: {} + + node-mock-http@1.0.4: {} + + normalize-path@3.0.0: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + ofetch@1.5.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.3 + + ohash@2.0.11: {} + + oniguruma-parser@0.12.1: {} + + oniguruma-to-es@4.3.4: + dependencies: + oniguruma-parser: 0.12.1 + regex: 6.1.0 + regex-recursion: 6.0.2 + + openapi-types@12.1.3: {} + + p-limit@6.2.0: + dependencies: + yocto-queue: 1.2.2 + + p-queue@8.1.1: + dependencies: + eventemitter3: 5.0.4 + p-timeout: 6.1.4 + + p-timeout@6.1.4: {} + + package-manager-detector@1.6.0: {} + + pagefind@1.4.0: + optionalDependencies: + "@pagefind/darwin-arm64": 1.4.0 + "@pagefind/darwin-x64": 1.4.0 + "@pagefind/freebsd-x64": 1.4.0 + "@pagefind/linux-arm64": 1.4.0 + "@pagefind/linux-x64": 1.4.0 + "@pagefind/windows-x64": 1.4.0 + + parse-entities@4.0.2: + dependencies: + "@types/unist": 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse-latin@7.0.0: + dependencies: + "@types/nlcst": 2.0.3 + "@types/unist": 3.0.3 + nlcst-to-string: 4.0.0 + unist-util-modify-children: 4.0.0 + unist-util-visit-children: 3.0.0 + vfile: 6.0.3 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + piccolore@0.1.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + postcss-nested@6.2.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prismjs@1.30.0: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + property-information@7.1.0: {} + + radix3@1.1.2: {} + + readdirp@5.0.0: {} + + recma-build-jsx@1.0.0: + dependencies: + "@types/estree": 1.0.8 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.1(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + + recma-parse@1.0.0: + dependencies: + "@types/estree": 1.0.8 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + "@types/estree": 1.0.8 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + rehype-expressive-code@0.41.6: + dependencies: + expressive-code: 0.41.6 + + rehype-format@5.0.1: + dependencies: + "@types/hast": 3.0.4 + hast-util-format: 1.1.0 + + rehype-parse@9.0.1: + dependencies: + "@types/hast": 3.0.4 + hast-util-from-html: 2.0.3 + unified: 11.0.5 + + rehype-raw@7.0.0: + dependencies: + "@types/hast": 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + rehype-recma@1.0.0: + dependencies: + "@types/estree": 1.0.8 + "@types/hast": 3.0.4 + hast-util-to-estree: 3.1.3 + transitivePeerDependencies: + - supports-color + + rehype-stringify@10.0.1: + dependencies: + "@types/hast": 3.0.4 + hast-util-to-html: 9.0.5 + unified: 11.0.5 + + rehype@13.0.2: + dependencies: + "@types/hast": 3.0.4 + rehype-parse: 9.0.1 + rehype-stringify: 10.0.1 + unified: 11.0.5 + + remark-directive@3.0.1: + dependencies: + "@types/mdast": 4.0.4 + mdast-util-directive: 3.1.0 + micromark-extension-directive: 3.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-gfm@4.0.1: + dependencies: + "@types/mdast": 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-mdx@3.1.1: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + "@types/mdast": 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + "@types/hast": 3.0.4 + "@types/mdast": 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-smartypants@3.0.2: + dependencies: + retext: 9.0.0 + retext-smartypants: 6.2.0 + unified: 11.0.5 + unist-util-visit: 5.1.0 + + remark-stringify@11.0.0: + dependencies: + "@types/mdast": 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + require-from-string@2.0.2: {} + + retext-latin@4.0.0: + dependencies: + "@types/nlcst": 2.0.3 + parse-latin: 7.0.0 + unified: 11.0.5 + + retext-smartypants@6.2.0: + dependencies: + "@types/nlcst": 2.0.3 + nlcst-to-string: 4.0.0 + unist-util-visit: 5.1.0 + + retext-stringify@4.0.0: + dependencies: + "@types/nlcst": 2.0.3 + nlcst-to-string: 4.0.0 + unified: 11.0.5 + + retext@9.0.0: + dependencies: + "@types/nlcst": 2.0.3 + retext-latin: 4.0.0 + retext-stringify: 4.0.0 + unified: 11.0.5 + + rollup@4.57.1: + dependencies: + "@types/estree": 1.0.8 + optionalDependencies: + "@rollup/rollup-android-arm-eabi": 4.57.1 + "@rollup/rollup-android-arm64": 4.57.1 + "@rollup/rollup-darwin-arm64": 4.57.1 + "@rollup/rollup-darwin-x64": 4.57.1 + "@rollup/rollup-freebsd-arm64": 4.57.1 + "@rollup/rollup-freebsd-x64": 4.57.1 + "@rollup/rollup-linux-arm-gnueabihf": 4.57.1 + "@rollup/rollup-linux-arm-musleabihf": 4.57.1 + "@rollup/rollup-linux-arm64-gnu": 4.57.1 + "@rollup/rollup-linux-arm64-musl": 4.57.1 + "@rollup/rollup-linux-loong64-gnu": 4.57.1 + "@rollup/rollup-linux-loong64-musl": 4.57.1 + "@rollup/rollup-linux-ppc64-gnu": 4.57.1 + "@rollup/rollup-linux-ppc64-musl": 4.57.1 + "@rollup/rollup-linux-riscv64-gnu": 4.57.1 + "@rollup/rollup-linux-riscv64-musl": 4.57.1 + "@rollup/rollup-linux-s390x-gnu": 4.57.1 + "@rollup/rollup-linux-x64-gnu": 4.57.1 + "@rollup/rollup-linux-x64-musl": 4.57.1 + "@rollup/rollup-openbsd-x64": 4.57.1 + "@rollup/rollup-openharmony-arm64": 4.57.1 + "@rollup/rollup-win32-arm64-msvc": 4.57.1 + "@rollup/rollup-win32-ia32-msvc": 4.57.1 + "@rollup/rollup-win32-x64-gnu": 4.57.1 + "@rollup/rollup-win32-x64-msvc": 4.57.1 + fsevents: 2.3.3 + + sax@1.4.4: {} + + semver@7.7.4: {} + + sharp@0.34.5: + dependencies: + "@img/colour": 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + "@img/sharp-darwin-arm64": 0.34.5 + "@img/sharp-darwin-x64": 0.34.5 + "@img/sharp-libvips-darwin-arm64": 1.2.4 + "@img/sharp-libvips-darwin-x64": 1.2.4 + "@img/sharp-libvips-linux-arm": 1.2.4 + "@img/sharp-libvips-linux-arm64": 1.2.4 + "@img/sharp-libvips-linux-ppc64": 1.2.4 + "@img/sharp-libvips-linux-riscv64": 1.2.4 + "@img/sharp-libvips-linux-s390x": 1.2.4 + "@img/sharp-libvips-linux-x64": 1.2.4 + "@img/sharp-libvips-linuxmusl-arm64": 1.2.4 + "@img/sharp-libvips-linuxmusl-x64": 1.2.4 + "@img/sharp-linux-arm": 0.34.5 + "@img/sharp-linux-arm64": 0.34.5 + "@img/sharp-linux-ppc64": 0.34.5 + "@img/sharp-linux-riscv64": 0.34.5 + "@img/sharp-linux-s390x": 0.34.5 + "@img/sharp-linux-x64": 0.34.5 + "@img/sharp-linuxmusl-arm64": 0.34.5 + "@img/sharp-linuxmusl-x64": 0.34.5 + "@img/sharp-wasm32": 0.34.5 + "@img/sharp-win32-arm64": 0.34.5 + "@img/sharp-win32-ia32": 0.34.5 + "@img/sharp-win32-x64": 0.34.5 + + shiki@3.22.0: + dependencies: + "@shikijs/core": 3.22.0 + "@shikijs/engine-javascript": 3.22.0 + "@shikijs/engine-oniguruma": 3.22.0 + "@shikijs/langs": 3.22.0 + "@shikijs/themes": 3.22.0 + "@shikijs/types": 3.22.0 + "@shikijs/vscode-textmate": 10.0.2 + "@types/hast": 3.0.4 + + sisteransi@1.0.5: {} + + sitemap@8.0.2: + dependencies: + "@types/node": 17.0.45 + "@types/sax": 1.2.7 + arg: 5.0.2 + sax: 1.4.4 + + smol-toml@1.6.0: {} + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + space-separated-tokens@2.0.2: {} + + starlight-openapi@0.22.0(@astrojs/markdown-remark@6.3.10)(@astrojs/starlight@0.37.6(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3)))(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3))(openapi-types@12.1.3): + dependencies: + "@astrojs/markdown-remark": 6.3.10 + "@astrojs/starlight": 0.37.6(astro@5.17.2(rollup@4.57.1)(typescript@5.9.3)) + "@readme/openapi-parser": 4.1.2(openapi-types@12.1.3) + astro: 5.17.2(rollup@4.57.1)(typescript@5.9.3) + github-slugger: 2.0.0 + url-template: 3.1.1 + transitivePeerDependencies: + - openapi-types + + stream-replace-string@2.0.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + svgo@4.0.0: + dependencies: + commander: 11.1.0 + css-select: 5.2.2 + css-tree: 3.1.0 + css-what: 6.2.2 + csso: 5.0.5 + picocolors: 1.1.1 + sax: 1.4.4 + + tiny-inflate@1.0.3: {} + + tinyexec@1.0.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + tsconfck@3.1.6(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 + + tslib@2.8.1: + optional: true + + type-fest@4.41.0: {} + + typescript@5.9.3: {} + + ufo@1.6.3: {} + + ultrahtml@1.6.0: {} + + uncrypto@0.1.3: {} + + unified@11.0.5: + dependencies: + "@types/unist": 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unifont@0.7.4: + dependencies: + css-tree: 3.1.0 + ofetch: 1.5.1 + ohash: 2.0.11 + + unist-util-find-after@5.0.0: + dependencies: + "@types/unist": 3.0.3 + unist-util-is: 6.0.1 + + unist-util-is@6.0.1: + dependencies: + "@types/unist": 3.0.3 + + unist-util-modify-children@4.0.0: + dependencies: + "@types/unist": 3.0.3 + array-iterate: 2.0.1 + + unist-util-position-from-estree@2.0.0: + dependencies: + "@types/unist": 3.0.3 + + unist-util-position@5.0.0: + dependencies: + "@types/unist": 3.0.3 + + unist-util-remove-position@5.0.0: + dependencies: + "@types/unist": 3.0.3 + unist-util-visit: 5.1.0 + + unist-util-stringify-position@4.0.0: + dependencies: + "@types/unist": 3.0.3 + + unist-util-visit-children@3.0.0: + dependencies: + "@types/unist": 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + "@types/unist": 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + "@types/unist": 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + unstorage@1.17.4: + dependencies: + anymatch: 3.1.3 + chokidar: 5.0.0 + destr: 2.0.5 + h3: 1.15.5 + lru-cache: 11.2.6 + node-fetch-native: 1.6.7 + ofetch: 1.5.1 + ufo: 1.6.3 + + url-template@3.1.1: {} + + util-deprecate@1.0.2: {} + + vfile-location@5.0.3: + dependencies: + "@types/unist": 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + "@types/unist": 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + "@types/unist": 3.0.3 + vfile-message: 4.0.3 + + vite@6.4.1: + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.57.1 + tinyglobby: 0.2.15 + optionalDependencies: + fsevents: 2.3.3 + + vitefu@1.1.1(vite@6.4.1): + optionalDependencies: + vite: 6.4.1 + + web-namespaces@2.0.1: {} + + which-pm-runs@1.1.0: {} + + widest-line@5.0.0: + dependencies: + string-width: 7.2.0 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 + + xxhash-wasm@1.1.0: {} + + yargs-parser@21.1.1: {} + + yocto-queue@1.2.2: {} + + yocto-spinner@0.2.3: + dependencies: + yoctocolors: 2.1.2 + + yoctocolors@2.1.2: {} + + zod-to-json-schema@3.25.1(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod-to-ts@1.2.0(typescript@5.9.3)(zod@3.25.76): + dependencies: + typescript: 5.9.3 + zod: 3.25.76 + + zod@3.25.76: {} + + zwitch@2.0.4: {} diff --git a/docs/postcss.config.js b/docs/postcss.config.js deleted file mode 100644 index b9f6f4b1..00000000 --- a/docs/postcss.config.js +++ /dev/null @@ -1,5 +0,0 @@ -/* eslint-disable */ - -module.exports = { - plugins: [require("tailwindcss")], -}; diff --git a/docs/src/public/favicon.ico b/docs/public/favicon.ico similarity index 100% rename from docs/src/public/favicon.ico rename to docs/public/favicon.ico diff --git a/docs/src/public/images/open-graph.jpg b/docs/public/open-graph.jpg similarity index 100% rename from docs/src/public/images/open-graph.jpg rename to docs/public/open-graph.jpg diff --git a/docs/src/.i18n-filter b/docs/src/.i18n-filter index 6e3dfc30..9871cee8 100644 --- a/docs/src/.i18n-filter +++ b/docs/src/.i18n-filter @@ -1,7 +1,9 @@ -public -contributing -getting-started -index.md +en +ca +de +es fr -pt-BR -nn-NO +nn-no +pt-br +sr-latn +zh-hans diff --git a/docs/src/ar/contributing/_category_.json b/docs/src/ar/contributing/_category_.json deleted file mode 100644 index e12f1ce5..00000000 --- a/docs/src/ar/contributing/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Contributing", - "position": 3 -} diff --git a/docs/src/ar/contributing/guidelines.md b/docs/src/ar/contributing/guidelines.md deleted file mode 100644 index 1a53c89e..00000000 --- a/docs/src/ar/contributing/guidelines.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -title: Guidelines ---- - -# Contributing to Castopod - -Love Castopod and want to help? Thanks so much, there's something to do for -everybody! - -Please take a moment to review this document in order to make the contribution -process easy and effective for everyone involved. - -Following these guidelines helps to communicate that you respect the time of the -developers managing and developing this open source project. In return, they -should reciprocate that respect in addressing your issue or assessing patches -and features. - -::: info Note - -**Any** contribution made on a repository other than -[the original repository](https://code.castopod.org/adaures/castopod) will not -be accepted. - -::: - -## Using the issue tracker - -The [issue tracker](https://code.castopod.org/adaures/castopod/-/issues) is the -preferred channel for [bug reports](#bug-reports), -[features requests](#feature-requests) and -[submitting pull requests](#pull-requests). - -## ⚠️ Security issues and vulnerabilities - -If you encounter any security issue or vulnerability in the Castopod source, -please contact us directly by email at -[security@castopod.org](mailto:security@castopod.org) - -## Bug reports - -A bug is a _demonstrable problem_ that is caused by the code in the repository. -Good bug reports are extremely helpful - thank you! - -Guidelines for bug reports: - -1. **Use the issue search** — check if the issue has already been - reported. - -2. **Check if the issue has been fixed** — try to reproduce it using the - latest `main` branch in the repository. - -3. **Isolate the problem** — ideally create a - [reduced test case](https://css-tricks.com/reduced-test-cases/) and a live - example. - -A good bug report shouldn't leave others needing to chase you up for more -information. Please try to be as detailed as possible in your report. What is -your environment? What steps will reproduce the issue? What browser(s) and OS -experience the problem? What would you expect to be the outcome? All these -details will help people to fix any potential bugs. - -> [Issue templates](https://docs.gitlab.com/ee/user/project/description_templates.html#using-the-templates) -> have been created for this project. You may use them to help you follow those -> guidelines. - -## Feature requests - -Feature requests are welcome. But take a moment to find out whether your idea -fits with the scope and aims of the project. It's up to _you_ to make a strong -case to convince the project's developers of the merits of this feature. Please -provide as much detail and context as possible. - -## Pull requests - -Good pull requests - patches, improvements, new features - are a fantastic help. -They should remain focused in scope and avoid containing unrelated commits. - -**Please ask first** before embarking on any significant pull request (e.g. -implementing features, refactoring code, porting to a different language), -otherwise you risk spending a lot of time working on something that the -project's developers might not want to merge into the project. - -Please adhere to the coding conventions used throughout a project (indentation, -accurate comments, etc.) and any other requirements (such as test coverage). - -Adhering to the following process is the best way to get your work included in -the project: - -1. [Fork](https://docs.gitlab.com/ee/gitlab-basics/fork-project.html) the - project, clone your fork, and configure the remotes: - -```bash -# Clone your fork of the repo into the current directory -git clone https://code.castopod.org//castopod.git - -# Navigate to the newly cloned directory -cd castopod - -# Assign the original repo to a remote called "upstream" -git remote add upstream https://code.castopod.org/adaures/castopod.git -``` - -2. If you cloned a while ago, get the latest changes from upstream: - -```bash -git checkout main -git pull upstream main -``` - -3. Create a new topic branch (off the `main` branch) to contain your feature, - change, or fix: - -```bash -git checkout -b -``` - -4. Commit your changes in logical chunks. Please adhere to these - [git commit message guidelines](https://conventionalcommits.org/) or your - code is unlikely be merged into the main project. Use Git's - [interactive rebase](https://help.github.com/articles/about-git-rebase/) - feature to tidy up your commits before making them public. - -5. Locally merge (or rebase) the upstream dev branch into your topic branch: - -```bash -git pull [--rebase] upstream main -``` - -6. Push your topic branch up to your fork: - -```bash -git push origin -``` - -7. [Open a Pull Request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html#new-merge-request-from-a-fork) - with a clear title and description. - -**IMPORTANT**: By submitting a patch, you agree to allow the project owners to -license your work under the terms of the -[GNU AGPLv3](https://code.castopod.org/adaures/castopod/-/blob/main/LICENSE). - -## Collaborating guidelines - -There are few basic rules to ensure high quality of the project: - -- Before merging, a PR requires at least two approvals from the collaborators - unless it's an architectural change, a large feature, etc. If it is, then at - least 50% of the core team have to agree to merge it, with every team member - having a full veto right. (i.e. every single one can block any PR) -- A PR should remain open for at least two days before merging (does not apply - for trivial contributions like fixing a typo). This way everyone has enough - time to look into it. - -You are always welcome to discuss and propose improvements to this guideline. diff --git a/docs/src/ar/getting-started/_category_.json b/docs/src/ar/getting-started/_category_.json deleted file mode 100644 index 877a378f..00000000 --- a/docs/src/ar/getting-started/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Getting Started", - "position": 2 -} diff --git a/docs/src/ar/getting-started/docker.md b/docs/src/ar/getting-started/docker.md deleted file mode 100644 index ededc582..00000000 --- a/docs/src/ar/getting-started/docker.md +++ /dev/null @@ -1,148 +0,0 @@ ---- -title: Official Docker images -sidebarDepth: 3 ---- - -# Official Docker images - -Castopod pushes 2 Docker images to the Docker Hub during its automated build -process: - -- [**`castopod/app`**](https://hub.docker.com/r/castopod/app): the app bundle - with all of Castopod dependencies -- [**`castopod/web-server`**](https://hub.docker.com/r/castopod/web-server): an - Nginx configuration for Castopod - -Additionally, Castopod requires a MySQL-compatible database. A Redis database -can be added as a cache handler. - -## Supported tags - -- `develop` [unstable], latest development branch build -- `beta` [stable], latest beta version build -- `1.0.0-beta.x` [stable], specific beta version build (since `1.0.0-beta.22`) - -## Example usage - -1. Install [docker](https://docs.docker.com/get-docker/) and - [docker-compose](https://docs.docker.com/compose/install/) -2. Create a `docker-compose.yml` file with the following: - - ```yml - version: "3.7" - - services: - app: - image: castopod/app:beta - container_name: "castopod-app" - volumes: - - castopod-media:/opt/castopod/public/media - environment: - MYSQL_DATABASE: castopod - MYSQL_USER: castopod - MYSQL_PASSWORD: changeme - CP_BASEURL: "http://castopod.example.com" - CP_ANALYTICS_SALT: changeme - CP_CACHE_HANDLER: redis - CP_REDIS_HOST: redis - networks: - - castopod-app - - castopod-db - restart: unless-stopped - - web-server: - image: castopod/web-server:beta - container_name: "castopod-web-server" - volumes: - - castopod-media:/var/www/html/media - networks: - - castopod-app - ports: - - 8080:80 - restart: unless-stopped - - mariadb: - image: mariadb:10.5 - container_name: "castopod-mariadb" - networks: - - castopod-db - volumes: - - castopod-db:/var/lib/mysql - environment: - MYSQL_ROOT_PASSWORD: changeme - MYSQL_DATABASE: castopod - MYSQL_USER: castopod - MYSQL_PASSWORD: changeme - restart: unless-stopped - - redis: - image: redis:7.0-alpine - container_name: "castopod-redis" - volumes: - - castopod-cache:/data - networks: - - castopod-app - - volumes: - castopod-media: - castopod-db: - castopod-cache: - - networks: - castopod-app: - castopod-db: - ``` - - You have to adapt some variables to your needs (e.g. `CP_BASEURL`, - `MYSQL_ROOT_PASSWORD`, `MYSQL_PASSWORD` and `CP_ANALYTICS_SALT`). - -3. Setup a reverse proxy for TLS (SSL/HTTPS) - - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): - - ``` - #castopod - castopod.example.com { - reverse_proxy localhost:8080 - } - ``` - -4. Run `docker-compose up -d`, wait for it to initialize and head on to - `https://castopod.example.com/cp-install` to finish setting up Castopod! - -5. You're all set, start podcasting! 🎙️🚀 - -## Environment Variables - -- **castopod/app** - - | Variable name | Type (`default`) | Default | - | ---------------------------- | ----------------------- | ---------------- | - | **`CP_BASEURL`** | string | `undefined` | - | **`CP_MEDIA_BASEURL`** | ?string | `CP_BASEURL` | - | **`CP_ADMIN_GATEWAY`** | ?string | `"cp-admin"` | - | **`CP_AUTH_GATEWAY`** | ?string | `"cp-auth"` | - | **`CP_ANALYTICS_SALT`** | string | `undefined` | - | **`CP_DATABASE_HOSTNAME`** | ?string | `"mariadb"` | - | **`CP_DATABASE_NAME`** | ?string | `MYSQL_DATABASE` | - | **`CP_DATABASE_USERNAME`** | ?string | `MYSQL_USER` | - | **`CP_DATABASE_PASSWORD`** | ?string | `MYSQL_PASSWORD` | - | **`CP_DATABASE_PREFIX`** | ?string | `"cp_"` | - | **`CP_CACHE_HANDLER`** | [`"file"` or `"redis"`] | `"file"` | - | **`CP_REDIS_HOST`** | ?string | `"localhost"` | - | **`CP_REDIS_PASSWORD`** | ?string | `null` | - | **`CP_REDIS_PORT`** | ?number | `6379` | - | **`CP_REDIS_DATABASE`** | ?number | `0` | - | **`CP_EMAIL_SMTP_HOST`** | ?string | `undefined` | - | **`CP_EMAIL_FROM`** | ?string | `undefined` | - | **`CP_EMAIL_SMTP_USERNAME`** | ?string | `"localhost"` | - | **`CP_EMAIL_SMTP_PASSWORD`** | ?string | `null` | - | **`CP_EMAIL_SMTP_PORT`** | ?number | `25` | - | **`CP_EMAIL_SMTP_CRYPTO`** | [`"tls"` or `"ssl"`] | `"tls"` | - -- **castopod/web-server** - - | Variable name | Type | Default | - | --------------------- | ------- | ------- | - | **`CP_APP_HOSTNAME`** | ?string | `"app"` | diff --git a/docs/src/ar/index.md b/docs/src/ar/index.md deleted file mode 100644 index d89daccb..00000000 --- a/docs/src/ar/index.md +++ /dev/null @@ -1,295 +0,0 @@ ---- -sidebarDepth: 2 ---- - -# Welcome 👋 - -[![release-badge]][release] [![license-badge]][license] [![contributions-badge]][contributions] [![semantic-release-badge]][semantic-release] [![crowdin-badge]][crowdin] [![discord-badge]][discord] [![stars-badge]][stars] - -Castopod is a free & open-source hosting platform made for podcasters who want -engage and interact with their audience. - -Castopod is easy to install and was built on top of -[CodeIgniter4](https://codeigniter.com/), a powerful PHP framework with a very -small footprint. - -::: info Status - -Castopod is currently in **beta** but already quite stable and used by -podcasters around the world! - -::: - -
    - Install -
    - -## Features - -- 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … -- 💬  Built-in social network: - - 🚀  Castopod is part of the Fediverse, a decentralized social network - - ❤️  Create posts, share, favourite, and comment on episodes -- 📈  Built-in analytics: - - ⚖️  GDPR / CCPA / LGPD compliant - - 🪙  Standard IABv2 audience measurement - - 🏡  On-premises analytics, no third party involved -- 📢  Built-in marketing tools: - - ✅  SEO ready (open-graph meta-tags, JSON-LD, …) - - 📱  PWA: install as a standalone app - - 🎨  Customizable theme colors - - 🎬  Generate ready-to-share Video clips from episodes - - 🔉  Generate soundbites - - ▶️  Embeddable player, embed your episodes on any website -- 💸  Monetization: - - 🔗  Funding links - - 📲  listen-to-click ads - - 🤝  value4value / WebMonetization - - 💎  Premium podcasts -- 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … - - ⚡  Broadcast your episodes instantly with WebSub -- 📥  Podcast import: move your existing podcast into Castopod -- 📤  Move your podcast out of Castopod -- 🔀  Multi-tenant: host as many podcasts as you want -- 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese & Spanish… with - [more to come](https://translate.castopod.org)! - -## Motivation - -The podcasting ecosystem is decentralized by nature: you can create your podcast -as an RSS file, publish it on the web and have it shared everywhere online. - -It is in fact one of the only media to have stayed this way for a long time. - -As usages are evolving, more and more people are getting into podcasts: whether -it is creators finding new ways to share their ideas, or listeners in the search -for better content. - -With podcasting becoming more widely used, some companies are trying to shift it -towards a more controlled and centralized medium. - -Castopod was created in an effort to provide an open and sustainable alternative -to hosting your podcasts, promoting decentralization to ensure that podcasters -creativity can express itself. - -This project is pushed by the open-source community, and specifically by the -[Fediverse](https://fediverse.party/en/fediverse/) and -[Podcasting 2.0](https://podcastindex.org/) movements. - -## Comparison with other solutions - -We believe that a solution is not necessarily right for everyone, it highly -depends on your needs. So, here are comparisons with other tools to help you to -gauge whether Castopod is the right fit for you. - -### Castopod vs Wordpress - -Castopod is often referred to as "the Wordpress for podcasts" because of the -similarities between the two. In some ways this is true. And actually, Castopod -was greatly inspired by the Wordpress ecosystem, seeing the ease of adoption -from the community and the number of websites running it. - -Just like Wordpress, Castopod is free & open source, built using PHP with a -MySQL database and is packaged in a way that you can easily install on most web -servers. - -Wordpress is a great way to create your website and extend it with plugins to -get what you want. It is a full fledged CMS that helps you get any type of -website online. - -On the other hand, Castopod is meant to address the podcasters needs -specifically, focusing on podcasting, and nothing else. You don't need any -plugin to get you started on your podcasting journey. - -This allows optimizing the processes specific to podcasting: ranging from the -creation of your podcasts and the publication of new episodes all the way to -broadcasting, marketing and analytics. - -Finally, depending on your needs, Wordpress and Castopod can even live side by -side as they share the same requirements! - -### Castopod vs Funkwhale - -Funkwhale is a self-hosted, modern free and open-source music server. Just as -Castopod, Funkwhale is on the fediverse, a decentralized social network allowing -interoperability between the two. - -Funkwhale was initially built around music. And later on, as the project -evolved, the ability to host podcasts was introduced. - -Unlike Funkwhale, Castopod has been designed and built around podcasting -exclusively. This allows easier implementation for features related to the -podcasting ecosystem, such as the podcasting 2.0 features (transcripts, -chapters, locations, persons, …). - -So, you should probably use Funkwhale if you want to host your music, and use -Castopod if you want to host your podcasts. - -### Castopod vs other podcast hosts - -There are many solutions for you to host your podcasts, some of which are really -great and [a lot of them](https://podcastindex.org/apps) are jumping into the -Podcasting 2.0 wagon just like Castopod! - -Each of these solutions differ from one another, you may compare with the -[list of features](#features). - -That being said, there are two main differences with other podcasting solutions: - -- Castopod can be self-hosted and is the only solution that allows you to keep - full control over what you produce. Also, as it is open-source, you can even - customize it as you wish. - -- Castopod is the only solution that currently integrates both a decentralized - social network with ActivityPub as well as many of the podcasting 2.0 - features, hoping to bridge the gap between the two. - -## Contributing - -Love Castopod and would like to help? Take a look at the following documentation -to get you started. - -### Code of conduct - -Castopod has adopted a Code of Conduct that we expect project participants to -adhere to. Please read the -[CODE_OF_CONDUCT manual](https://code.castopod.org/adaures/castopod/-/blob/beta/CODE_OF_CONDUCT.md) -so that you can understand what actions will and will not be tolerated. - -### Contributing guide - -Read our [contributing guide](./contributing/guidelines.md) to learn about our -development process, how to propose bugfixes and improvements, and how to build -and test your changes to Castopod. - -## Contributors ✨ - -Thanks goes to these wonderful people -([emoji key](https://allcontributors.org/docs/en/emoji-key)): - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Yassine Doghri

    💻 🐛 📖 👀 🚧 🖋 🎨 ️️️️♿️ 🌍 💬 🧑‍🏫 🚇 🤔 📆 📝

    Benjamin Bellamy

    💻 🐛 👀 🖋 🌍 💬 🚇 🤔 📝 📆 📢

    Ola Hneini

    💻 👀 📖 🚧 💬 🤔

    Romain de Laage

    💻 🚇 📖 🌍 🤔

    Lyonel Bernard

    🐛 💬 🔊 🤔

    Christopher Lagonick-Weitzel

    🐛 💬 🔊 🤔

    Ernesto Acosta

    🐛 🔊 🌍 💬 🤔

    Bastien Luneteau

    💻 🐛

    Cécile Ricordeau

    🎨

    Patryk Miś

    🌍

    Marcin Lewandowski

    🐛 🤔

    Sebastian Janik

    💻

    Patryk Karczmarczyk

    💻

    denis d

    🐛 🤔

    Douglas Kastle

    🐛 🤔

    cExplorer

    🐛 🌍

    ImaCrea

    🐛 🤔

    Jonas S

    💻

    LEFEBVRE Yann

    🐛

    Sebastian Späth

    🐛 🤔

    rocky III

    🐛

    Hermann Josef Eckl

    🐛

    Delhaye Cyrille

    🐛 🤔

    João Leandro

    🌍 🤔

    Angelos Chouvardas

    🌍

    Eivind

    🌍

    Ewen

    🌍 🤔

    forght

    🌍

    glottis0q

    🌍

    ButterflyOfFire

    🌍

    Lucian I. Last

    🌍

    LuuzViir

    🌍

    CTHTC

    🌍

    Russian Retro

    🌍

    Marek L'ach

    🌍

    GunChleoc

    🌍

    GabiSnow

    🌍

    bendaha

    🌍

    Samuel Roland

    🌍

    Dimitri Regnier

    🤔

    irithys

    🌍

    Sergi

    🌍

    ghose (XoseM)

    🌍
    - - - - - - -This project follows the -[all-contributors](https://github.com/all-contributors/all-contributors) -specification. Contributions of any kind welcome! - -## Contact - -You may reach us for help or ask any question you have on: - -- [Discord](https://castopod.org/discord) (for direct interaction with - developers and the community) -- [Issue tracker](https://code.castopod.org/adaures/castopod/-/issues) (for - feature requests & bug reports) - -Alternatively, you can follow us on social media platforms to get news about -Castopod: - -- [podlibre.social](https://podlibre.social/@Castopod) (Mastodon instance) -- [Twitter](https://twitter.com/castopod) -- [LinkedIn](https://linkedin.com/company/castopod) -- [Facebook](https://www.facebook.com/castopod) - -## Sponsors - -The ongoing development of Castopod is made possible with the support of its -backers. If you'd like to help, please consider -[sponsoring Castopod's development](https://opencollective.com/castopod/contribute). - -
    - Ad Aures Logo - NLnet Logo -
    - -## License - -[GNU Affero General Public License v3.0](https://choosealicense.com/licenses/agpl-3.0/) - -Copyright © 2020-present, [Ad Aures](https://adaures.com/). -https://img.shields.io/gitlab/v/release/2?color=brightgreen&gitlab_url=https%3A%2F%2Fcode.castopod.org%2F&include_prereleases&label=release -https://img.shields.io/github/license/ad-aures/castopod?color=blue -https://img.shields.io/badge/contributions-welcome-brightgreen.svg -https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg -https://img.shields.io/github/stars/ad-aures/castopod?style=social - -[release]: https://code.castopod.org/adaures/castopod/-/releases -[license]: https://code.castopod.org/adaures/castopod/-/blob/beta/LICENSE.md -[contributions]: https://code.castopod.org/adaures/castopod/-/issues -[semantic-release]: https://github.com/semantic-release/semantic-release -[discord]: https://castopod.org/discord -[stars]: https://github.com/ad-aures/castopod/stargazers -[crowdin]: https://translate.castopod.org/project/castopod diff --git a/docs/src/public/images/castopod-logo-inline.svg b/docs/src/assets/castopod-logo-inline.svg similarity index 100% rename from docs/src/public/images/castopod-logo-inline.svg rename to docs/src/assets/castopod-logo-inline.svg diff --git a/docs/src/assets/images/sponsors/adaures.svg b/docs/src/assets/images/sponsors/adaures.svg new file mode 100644 index 00000000..aded8d98 --- /dev/null +++ b/docs/src/assets/images/sponsors/adaures.svg @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/docs/src/assets/images/sponsors/nlnet.svg b/docs/src/assets/images/sponsors/nlnet.svg new file mode 100644 index 00000000..5fa21021 --- /dev/null +++ b/docs/src/assets/images/sponsors/nlnet.svg @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/docs/src/br/contributing/_category_.json b/docs/src/br/contributing/_category_.json deleted file mode 100644 index e12f1ce5..00000000 --- a/docs/src/br/contributing/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Contributing", - "position": 3 -} diff --git a/docs/src/br/contributing/guidelines.md b/docs/src/br/contributing/guidelines.md deleted file mode 100644 index 1a53c89e..00000000 --- a/docs/src/br/contributing/guidelines.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -title: Guidelines ---- - -# Contributing to Castopod - -Love Castopod and want to help? Thanks so much, there's something to do for -everybody! - -Please take a moment to review this document in order to make the contribution -process easy and effective for everyone involved. - -Following these guidelines helps to communicate that you respect the time of the -developers managing and developing this open source project. In return, they -should reciprocate that respect in addressing your issue or assessing patches -and features. - -::: info Note - -**Any** contribution made on a repository other than -[the original repository](https://code.castopod.org/adaures/castopod) will not -be accepted. - -::: - -## Using the issue tracker - -The [issue tracker](https://code.castopod.org/adaures/castopod/-/issues) is the -preferred channel for [bug reports](#bug-reports), -[features requests](#feature-requests) and -[submitting pull requests](#pull-requests). - -## ⚠️ Security issues and vulnerabilities - -If you encounter any security issue or vulnerability in the Castopod source, -please contact us directly by email at -[security@castopod.org](mailto:security@castopod.org) - -## Bug reports - -A bug is a _demonstrable problem_ that is caused by the code in the repository. -Good bug reports are extremely helpful - thank you! - -Guidelines for bug reports: - -1. **Use the issue search** — check if the issue has already been - reported. - -2. **Check if the issue has been fixed** — try to reproduce it using the - latest `main` branch in the repository. - -3. **Isolate the problem** — ideally create a - [reduced test case](https://css-tricks.com/reduced-test-cases/) and a live - example. - -A good bug report shouldn't leave others needing to chase you up for more -information. Please try to be as detailed as possible in your report. What is -your environment? What steps will reproduce the issue? What browser(s) and OS -experience the problem? What would you expect to be the outcome? All these -details will help people to fix any potential bugs. - -> [Issue templates](https://docs.gitlab.com/ee/user/project/description_templates.html#using-the-templates) -> have been created for this project. You may use them to help you follow those -> guidelines. - -## Feature requests - -Feature requests are welcome. But take a moment to find out whether your idea -fits with the scope and aims of the project. It's up to _you_ to make a strong -case to convince the project's developers of the merits of this feature. Please -provide as much detail and context as possible. - -## Pull requests - -Good pull requests - patches, improvements, new features - are a fantastic help. -They should remain focused in scope and avoid containing unrelated commits. - -**Please ask first** before embarking on any significant pull request (e.g. -implementing features, refactoring code, porting to a different language), -otherwise you risk spending a lot of time working on something that the -project's developers might not want to merge into the project. - -Please adhere to the coding conventions used throughout a project (indentation, -accurate comments, etc.) and any other requirements (such as test coverage). - -Adhering to the following process is the best way to get your work included in -the project: - -1. [Fork](https://docs.gitlab.com/ee/gitlab-basics/fork-project.html) the - project, clone your fork, and configure the remotes: - -```bash -# Clone your fork of the repo into the current directory -git clone https://code.castopod.org//castopod.git - -# Navigate to the newly cloned directory -cd castopod - -# Assign the original repo to a remote called "upstream" -git remote add upstream https://code.castopod.org/adaures/castopod.git -``` - -2. If you cloned a while ago, get the latest changes from upstream: - -```bash -git checkout main -git pull upstream main -``` - -3. Create a new topic branch (off the `main` branch) to contain your feature, - change, or fix: - -```bash -git checkout -b -``` - -4. Commit your changes in logical chunks. Please adhere to these - [git commit message guidelines](https://conventionalcommits.org/) or your - code is unlikely be merged into the main project. Use Git's - [interactive rebase](https://help.github.com/articles/about-git-rebase/) - feature to tidy up your commits before making them public. - -5. Locally merge (or rebase) the upstream dev branch into your topic branch: - -```bash -git pull [--rebase] upstream main -``` - -6. Push your topic branch up to your fork: - -```bash -git push origin -``` - -7. [Open a Pull Request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html#new-merge-request-from-a-fork) - with a clear title and description. - -**IMPORTANT**: By submitting a patch, you agree to allow the project owners to -license your work under the terms of the -[GNU AGPLv3](https://code.castopod.org/adaures/castopod/-/blob/main/LICENSE). - -## Collaborating guidelines - -There are few basic rules to ensure high quality of the project: - -- Before merging, a PR requires at least two approvals from the collaborators - unless it's an architectural change, a large feature, etc. If it is, then at - least 50% of the core team have to agree to merge it, with every team member - having a full veto right. (i.e. every single one can block any PR) -- A PR should remain open for at least two days before merging (does not apply - for trivial contributions like fixing a typo). This way everyone has enough - time to look into it. - -You are always welcome to discuss and propose improvements to this guideline. diff --git a/docs/src/br/contributing/setup-development.md b/docs/src/br/contributing/setup-development.md deleted file mode 100644 index 6aafb50b..00000000 --- a/docs/src/br/contributing/setup-development.md +++ /dev/null @@ -1,423 +0,0 @@ ---- -title: Development setup -sidebarDepth: 3 ---- - -# Setup your development environment - -## Introduction - -Castopod is a web app based on the `php` framework -[CodeIgniter 4](https://codeigniter.com). - -We use [Docker](https://www.docker.com/) quickly setup a dev environment. A -`docker-compose.yml` and `Dockerfile` are included in the project's root folder -to help you kickstart your contribution. - -> You don't need any prior knowledge of Docker to follow the next steps. -> However, if you wish to use your own environment, feel free to do so! - -## Setup instructions - -### 1. Pre-requisites - -0. Install [docker](https://docs.docker.com/get-docker). - -1. Clone Castopod project by running: - - ```bash - git clone https://code.castopod.org/adaures/castopod.git - ``` - -2. Create a `.env` file with the minimum required config to connect the app to - the database and use redis as a cache handler: - - ```ini - CI_ENVIRONMENT="development" - # If set to development, you must run `npm run dev` to start the static assets server - vite.environment="development" - - # By default, this is set to true in the app config. - # For development, this must be set to false as it is - # on a local environment - app.forceGlobalSecureRequests=false - - app.baseURL="http://localhost:8080/" - app.mediaBaseURL="http://localhost:8080/" - - admin.gateway="cp-admin" - auth.gateway="cp-auth" - - database.default.hostname="mariadb" - database.default.database="castopod" - database.default.username="castopod" - database.default.password="castopod" - - cache.handler="redis" - cache.redis.host = "redis" - - # You may not want to use redis as your cache handler - # Comment/remove the two lines above and uncomment - # the next line for file caching. - #cache.handler="file" - ``` - - > _NB._ You can tweak your environment by setting more environment variables - > in your custom `.env` file. See the `env` for examples or the - > [CodeIgniter4 User Guide](https://codeigniter.com/user_guide/index.html) - > for more info. - -3. (for docker desktop) Add the repository you've cloned to docker desktop's - `Settings` > `Resources` > `File Sharing` - -### 2. (recommended) Develop inside the app Container with VSCode - -If you're working in VSCode, you can take advantage of the `.devcontainer/` -folder. It defines a development environment (dev container) with preinstalled -requirements and VSCode extensions so you don't have to worry about them. All -required services will be loaded automagically! 🪄 - -1. Install the VSCode extension - [Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) -2. `Ctrl/Cmd + Shift + P` > `Open in container` - - > The VSCode window will reload inside the dev container. Expect several - > minutes during first load as it is building all necessary services. - - **Note**: The dev container will start by running Castopod's php server. - During development, you will have to start [Vite](https://vitejs.dev)'s dev - server for compiling the typescript code and styles: - - ```bash - # run Vite dev server - npm run dev - ``` - - If there is any issue with the php server not running, you can restart them - using the following commands: - - ```bash - # run Castopod server - php spark serve - 0.0.0.0 - ``` - -3. You're all set! 🎉 - - You're now **inside the dev container**, you may use the VSCode console - (`Terminal` > `New Terminal`) to run any command: - - ```bash - # PHP is installed - php -v - - # Composer is installed - composer -V - - # npm is installed - npm -v - - # git is installed - git version - ``` - -For more info, see -[VSCode Remote Containers](https://code.visualstudio.com/docs/remote/containers) - -### 3. Start hacking - -You're all set! Start working your magic by updating the project's files! Help -yourself to the -[CodeIgniter4 User Guide](https://codeigniter.com/user_guide/index.html) for -more insights. - -To see your changes, go to: - -- `http://localhost:8080/` for the Castopod app -- `http://localhost:8888/` for the phpmyadmin interface: - - - username: **castopod** - - password: **castopod** - -### 2-alt. Develop outside the app container - -You do not wish to use the VSCode devcontainer? No problem! - -1. Start docker containers manually: - - Go to project's root folder and run: - - ```bash - # starts all services declared in docker-compose.yml file - # -d option starts the containers in the background - docker-compose up -d - - # See all running processes (you should see 3 processes running) - docker-compose ps - - # Alternatively, you can check all docker processes - docker ps -a - - ``` - - > The `docker-compose up -d` command will boot 4 containers in the - > background: - > - > - `castopod_app`: a php based container with Castopod requirements - > installed - > - `castopod_redis`: a [redis](https://redis.io/) database to handle queries - > and pages caching - > - `castopod_mariadb`: a [mariadb](https://mariadb.org/) server for - > persistent data - > - `castopod_phpmyadmin`: a phpmyadmin server to visualize the mariadb - > database. - -2. Run any command inside the containers by prefixing them with - `docker-compose run --rm app`: - - ```bash - # use PHP - docker-compose run --rm app php -v - - # use Composer - docker-compose run --rm app composer -V - - # use npm - docker-compose run --rm app npm -v - - # use git - docker-compose run --rm app git version - ``` - ---- - -## Going Further - -### Install Castopod's dependencies - -1. Install php dependencies with [Composer](https://getcomposer.org/) - - ```bash - composer install - ``` - - ::: info Note - - The php dependencies aren't included in the repository. Composer will check - the `composer.json` and `composer.lock` files to download the packages with - the right versions. The dependencies will live under the `vendor/` folder. - For more info, check out the - [Composer documentation](https://getcomposer.org/doc/). - - ::: - -2. Install javascript dependencies with [npm](https://www.npmjs.com/) - - ```bash - npm install - ``` - - ::: info Note - - The javascript dependencies aren't included in the repository. Npm will check - the `package.json` and `package.lock` files to download the packages with the - right versions. The dependencies will live under the `node_module` folder. - For more info, check out the [NPM documentation](https://docs.npmjs.com/). - - ::: - -3. Generate static assets: - - ```bash - # build all static assets at once - npm run build:static - - # build specific assets - npm run build:icons - npm run build:svg - ``` - - ::: info Note - - The static assets generated live under the `public/assets` folder, it - includes javascript, styles, images, fonts, icons and svg files. - - ::: - -### Initialize and populate database - -::: tip Tip - -You may skip this section if you go through the install wizard (go to -`/cp-install`). - -::: - -1. Build the database with the migrate command: - - ```bash - # loads the database schema during first migration - php spark migrate -all - ``` - - You may need to undo the migration (rollback): - - ```bash - # rolls back database schema (deletes all tables and their content) - php spark migrate:rollback - ``` - -2. Populate the database with the required data: - - ```bash - # Populates all required data - php spark db:seed AppSeeder - ``` - - You may choose to add data separately: - - ```bash - # Populates all categories - php spark db:seed CategorySeeder - - # Populates all Languages - php spark db:seed LanguageSeeder - - # Populates all podcasts platforms - php spark db:seed PlatformSeeder - - # Populates all Authentication data (roles definition…) - php spark db:seed AuthSeeder - ``` - -3. (optionnal) Populate the database with test data: - - - Populate test data (login: admin / password: AGUehL3P) - - ```bash - php spark db:seed TestSeeder - ``` - - - Populate with fake podcast analytics: - - ```bash - php spark db:seed FakePodcastsAnalyticsSeeder - ``` - - - Populate with fake website analytics: - - ```bash - php spark db:seed FakeWebsiteAnalyticsSeeder - ``` - - TestSeeder will add an active superadmin user with the following credentials: - - - username: **admin** - - password: **AGUehL3P** - -### Useful docker / docker-compose commands - -- Monitor the app container: - -```bash -docker-compose logs --tail 50 --follow --timestamps app -``` - -- Interact with redis server using included redis-cli command: - -```bash -docker exec -it castopod_redis redis-cli -``` - -- Monitor the redis container: - -```bash -docker-compose logs --tail 50 --follow --timestamps redis -``` - -- Monitor the mariadb container: - -```bash -docker-compose logs --tail 50 --follow --timestamps mariadb -``` - -- Monitor the phpmyadmin container: - -```bash -docker-compose logs --tail 50 --follow --timestamps phpmyadmin -``` - -- Restart docker containers: - -```bash -docker-compose restart -``` - -- Destroy all containers, opposite of `up` command: - -```bash -docker-compose down -``` - -- Rebuild app container: - -```bash -docker-compose build app -``` - -Check [docker](https://docs.docker.com/engine/reference/commandline/docker/) and -[docker-compose](https://docs.docker.com/compose/reference/) documentations for -more insights. - -## Known issues - -### Allocation failed - JavaScript heap out of memory - -This happens when running `npm install`. - -👉 By default, docker might not have access to enough RAM. Allocate more memory -and run `npm install` again. - -### (Linux) Files created inside container are attributed to root locally - -You may use Linux user namespaces to fix this on your machine: - -::: info Note - -Replace "username" with your local username - -::: - -1. Go to `/etc/docker/daemon.json` and add: - - ```json - { - "userns-remap": "username" - } - ``` - -2. Configure the subordinate uid/guid: - - ```bash - # in /etc/subuid - username:1000:1 - username:100000:65536 - ``` - - ```bash - # in /etc/subgid - username:1000:1 - username:100000:65536 - ``` - -3. Restart docker: - - ```bash - sudo systemctl restart docker - ``` - -4. That's it! Now, the root user in the container will be mapped to the user on - your local machine, no more permission issues! 🎉 - -You can check -[this great article](https://www.jujens.eu/posts/en/2017/Jul/02/docker-userns-remap/) -to know more about how it works. diff --git a/docs/src/br/getting-started/_category_.json b/docs/src/br/getting-started/_category_.json deleted file mode 100644 index 877a378f..00000000 --- a/docs/src/br/getting-started/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Getting Started", - "position": 2 -} diff --git a/docs/src/br/getting-started/docker.md b/docs/src/br/getting-started/docker.md deleted file mode 100644 index ededc582..00000000 --- a/docs/src/br/getting-started/docker.md +++ /dev/null @@ -1,148 +0,0 @@ ---- -title: Official Docker images -sidebarDepth: 3 ---- - -# Official Docker images - -Castopod pushes 2 Docker images to the Docker Hub during its automated build -process: - -- [**`castopod/app`**](https://hub.docker.com/r/castopod/app): the app bundle - with all of Castopod dependencies -- [**`castopod/web-server`**](https://hub.docker.com/r/castopod/web-server): an - Nginx configuration for Castopod - -Additionally, Castopod requires a MySQL-compatible database. A Redis database -can be added as a cache handler. - -## Supported tags - -- `develop` [unstable], latest development branch build -- `beta` [stable], latest beta version build -- `1.0.0-beta.x` [stable], specific beta version build (since `1.0.0-beta.22`) - -## Example usage - -1. Install [docker](https://docs.docker.com/get-docker/) and - [docker-compose](https://docs.docker.com/compose/install/) -2. Create a `docker-compose.yml` file with the following: - - ```yml - version: "3.7" - - services: - app: - image: castopod/app:beta - container_name: "castopod-app" - volumes: - - castopod-media:/opt/castopod/public/media - environment: - MYSQL_DATABASE: castopod - MYSQL_USER: castopod - MYSQL_PASSWORD: changeme - CP_BASEURL: "http://castopod.example.com" - CP_ANALYTICS_SALT: changeme - CP_CACHE_HANDLER: redis - CP_REDIS_HOST: redis - networks: - - castopod-app - - castopod-db - restart: unless-stopped - - web-server: - image: castopod/web-server:beta - container_name: "castopod-web-server" - volumes: - - castopod-media:/var/www/html/media - networks: - - castopod-app - ports: - - 8080:80 - restart: unless-stopped - - mariadb: - image: mariadb:10.5 - container_name: "castopod-mariadb" - networks: - - castopod-db - volumes: - - castopod-db:/var/lib/mysql - environment: - MYSQL_ROOT_PASSWORD: changeme - MYSQL_DATABASE: castopod - MYSQL_USER: castopod - MYSQL_PASSWORD: changeme - restart: unless-stopped - - redis: - image: redis:7.0-alpine - container_name: "castopod-redis" - volumes: - - castopod-cache:/data - networks: - - castopod-app - - volumes: - castopod-media: - castopod-db: - castopod-cache: - - networks: - castopod-app: - castopod-db: - ``` - - You have to adapt some variables to your needs (e.g. `CP_BASEURL`, - `MYSQL_ROOT_PASSWORD`, `MYSQL_PASSWORD` and `CP_ANALYTICS_SALT`). - -3. Setup a reverse proxy for TLS (SSL/HTTPS) - - TLS is mandatory for ActivityPub to work. This job can easily be handled by - a reverse proxy, for example with [Caddy](https://caddyserver.com/): - - ``` - #castopod - castopod.example.com { - reverse_proxy localhost:8080 - } - ``` - -4. Run `docker-compose up -d`, wait for it to initialize and head on to - `https://castopod.example.com/cp-install` to finish setting up Castopod! - -5. You're all set, start podcasting! 🎙️🚀 - -## Environment Variables - -- **castopod/app** - - | Variable name | Type (`default`) | Default | - | ---------------------------- | ----------------------- | ---------------- | - | **`CP_BASEURL`** | string | `undefined` | - | **`CP_MEDIA_BASEURL`** | ?string | `CP_BASEURL` | - | **`CP_ADMIN_GATEWAY`** | ?string | `"cp-admin"` | - | **`CP_AUTH_GATEWAY`** | ?string | `"cp-auth"` | - | **`CP_ANALYTICS_SALT`** | string | `undefined` | - | **`CP_DATABASE_HOSTNAME`** | ?string | `"mariadb"` | - | **`CP_DATABASE_NAME`** | ?string | `MYSQL_DATABASE` | - | **`CP_DATABASE_USERNAME`** | ?string | `MYSQL_USER` | - | **`CP_DATABASE_PASSWORD`** | ?string | `MYSQL_PASSWORD` | - | **`CP_DATABASE_PREFIX`** | ?string | `"cp_"` | - | **`CP_CACHE_HANDLER`** | [`"file"` or `"redis"`] | `"file"` | - | **`CP_REDIS_HOST`** | ?string | `"localhost"` | - | **`CP_REDIS_PASSWORD`** | ?string | `null` | - | **`CP_REDIS_PORT`** | ?number | `6379` | - | **`CP_REDIS_DATABASE`** | ?number | `0` | - | **`CP_EMAIL_SMTP_HOST`** | ?string | `undefined` | - | **`CP_EMAIL_FROM`** | ?string | `undefined` | - | **`CP_EMAIL_SMTP_USERNAME`** | ?string | `"localhost"` | - | **`CP_EMAIL_SMTP_PASSWORD`** | ?string | `null` | - | **`CP_EMAIL_SMTP_PORT`** | ?number | `25` | - | **`CP_EMAIL_SMTP_CRYPTO`** | [`"tls"` or `"ssl"`] | `"tls"` | - -- **castopod/web-server** - - | Variable name | Type | Default | - | --------------------- | ------- | ------- | - | **`CP_APP_HOSTNAME`** | ?string | `"app"` | diff --git a/docs/src/br/index.md b/docs/src/br/index.md deleted file mode 100644 index d89daccb..00000000 --- a/docs/src/br/index.md +++ /dev/null @@ -1,295 +0,0 @@ ---- -sidebarDepth: 2 ---- - -# Welcome 👋 - -[![release-badge]][release] [![license-badge]][license] [![contributions-badge]][contributions] [![semantic-release-badge]][semantic-release] [![crowdin-badge]][crowdin] [![discord-badge]][discord] [![stars-badge]][stars] - -Castopod is a free & open-source hosting platform made for podcasters who want -engage and interact with their audience. - -Castopod is easy to install and was built on top of -[CodeIgniter4](https://codeigniter.com/), a powerful PHP framework with a very -small footprint. - -::: info Status - -Castopod is currently in **beta** but already quite stable and used by -podcasters around the world! - -::: - -
    - Install -
    - -## Features - -- 🌱  Free & open-source (AGPL v3 License) -- 🔐  Focused on data sovereignty: your content, audience, and analytics - belong to you, and you only -- 🪄  Podcasting 2.0 features: GUID, locked, transcripts, funding, - chapters, location, persons, soundbites, … -- 💬  Built-in social network: - - 🚀  Castopod is part of the Fediverse, a decentralized social network - - ❤️  Create posts, share, favourite, and comment on episodes -- 📈  Built-in analytics: - - ⚖️  GDPR / CCPA / LGPD compliant - - 🪙  Standard IABv2 audience measurement - - 🏡  On-premises analytics, no third party involved -- 📢  Built-in marketing tools: - - ✅  SEO ready (open-graph meta-tags, JSON-LD, …) - - 📱  PWA: install as a standalone app - - 🎨  Customizable theme colors - - 🎬  Generate ready-to-share Video clips from episodes - - 🔉  Generate soundbites - - ▶️  Embeddable player, embed your episodes on any website -- 💸  Monetization: - - 🔗  Funding links - - 📲  listen-to-click ads - - 🤝  value4value / WebMonetization - - 💎  Premium podcasts -- 📡  Publish your episodes everywhere with RSS: - - 📱  On all indexes and apps: Podcast Index, Apple Podcasts, Spotify, - Google Podcasts, Deezer, Podcast Addict, Podfriend, … - - ⚡  Broadcast your episodes instantly with WebSub -- 📥  Podcast import: move your existing podcast into Castopod -- 📤  Move your podcast out of Castopod -- 🔀  Multi-tenant: host as many podcasts as you want -- 👥  Multi-user: add contributors and set roles -- 🌎  i18n support: translated in English, French, Polish, German, - Brazilian Portuguese & Spanish… with - [more to come](https://translate.castopod.org)! - -## Motivation - -The podcasting ecosystem is decentralized by nature: you can create your podcast -as an RSS file, publish it on the web and have it shared everywhere online. - -It is in fact one of the only media to have stayed this way for a long time. - -As usages are evolving, more and more people are getting into podcasts: whether -it is creators finding new ways to share their ideas, or listeners in the search -for better content. - -With podcasting becoming more widely used, some companies are trying to shift it -towards a more controlled and centralized medium. - -Castopod was created in an effort to provide an open and sustainable alternative -to hosting your podcasts, promoting decentralization to ensure that podcasters -creativity can express itself. - -This project is pushed by the open-source community, and specifically by the -[Fediverse](https://fediverse.party/en/fediverse/) and -[Podcasting 2.0](https://podcastindex.org/) movements. - -## Comparison with other solutions - -We believe that a solution is not necessarily right for everyone, it highly -depends on your needs. So, here are comparisons with other tools to help you to -gauge whether Castopod is the right fit for you. - -### Castopod vs Wordpress - -Castopod is often referred to as "the Wordpress for podcasts" because of the -similarities between the two. In some ways this is true. And actually, Castopod -was greatly inspired by the Wordpress ecosystem, seeing the ease of adoption -from the community and the number of websites running it. - -Just like Wordpress, Castopod is free & open source, built using PHP with a -MySQL database and is packaged in a way that you can easily install on most web -servers. - -Wordpress is a great way to create your website and extend it with plugins to -get what you want. It is a full fledged CMS that helps you get any type of -website online. - -On the other hand, Castopod is meant to address the podcasters needs -specifically, focusing on podcasting, and nothing else. You don't need any -plugin to get you started on your podcasting journey. - -This allows optimizing the processes specific to podcasting: ranging from the -creation of your podcasts and the publication of new episodes all the way to -broadcasting, marketing and analytics. - -Finally, depending on your needs, Wordpress and Castopod can even live side by -side as they share the same requirements! - -### Castopod vs Funkwhale - -Funkwhale is a self-hosted, modern free and open-source music server. Just as -Castopod, Funkwhale is on the fediverse, a decentralized social network allowing -interoperability between the two. - -Funkwhale was initially built around music. And later on, as the project -evolved, the ability to host podcasts was introduced. - -Unlike Funkwhale, Castopod has been designed and built around podcasting -exclusively. This allows easier implementation for features related to the -podcasting ecosystem, such as the podcasting 2.0 features (transcripts, -chapters, locations, persons, …). - -So, you should probably use Funkwhale if you want to host your music, and use -Castopod if you want to host your podcasts. - -### Castopod vs other podcast hosts - -There are many solutions for you to host your podcasts, some of which are really -great and [a lot of them](https://podcastindex.org/apps) are jumping into the -Podcasting 2.0 wagon just like Castopod! - -Each of these solutions differ from one another, you may compare with the -[list of features](#features). - -That being said, there are two main differences with other podcasting solutions: - -- Castopod can be self-hosted and is the only solution that allows you to keep - full control over what you produce. Also, as it is open-source, you can even - customize it as you wish. - -- Castopod is the only solution that currently integrates both a decentralized - social network with ActivityPub as well as many of the podcasting 2.0 - features, hoping to bridge the gap between the two. - -## Contributing - -Love Castopod and would like to help? Take a look at the following documentation -to get you started. - -### Code of conduct - -Castopod has adopted a Code of Conduct that we expect project participants to -adhere to. Please read the -[CODE_OF_CONDUCT manual](https://code.castopod.org/adaures/castopod/-/blob/beta/CODE_OF_CONDUCT.md) -so that you can understand what actions will and will not be tolerated. - -### Contributing guide - -Read our [contributing guide](./contributing/guidelines.md) to learn about our -development process, how to propose bugfixes and improvements, and how to build -and test your changes to Castopod. - -## Contributors ✨ - -Thanks goes to these wonderful people -([emoji key](https://allcontributors.org/docs/en/emoji-key)): - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Yassine Doghri

    💻 🐛 📖 👀 🚧 🖋 🎨 ️️️️♿️ 🌍 💬 🧑‍🏫 🚇 🤔 📆 📝

    Benjamin Bellamy

    💻 🐛 👀 🖋 🌍 💬 🚇 🤔 📝 📆 📢

    Ola Hneini

    💻 👀 📖 🚧 💬 🤔

    Romain de Laage

    💻 🚇 📖 🌍 🤔

    Lyonel Bernard

    🐛 💬 🔊 🤔

    Christopher Lagonick-Weitzel

    🐛 💬 🔊 🤔

    Ernesto Acosta

    🐛 🔊 🌍 💬 🤔

    Bastien Luneteau

    💻 🐛

    Cécile Ricordeau

    🎨

    Patryk Miś

    🌍

    Marcin Lewandowski

    🐛 🤔

    Sebastian Janik

    💻

    Patryk Karczmarczyk

    💻

    denis d

    🐛 🤔

    Douglas Kastle

    🐛 🤔

    cExplorer

    🐛 🌍

    ImaCrea

    🐛 🤔

    Jonas S

    💻

    LEFEBVRE Yann

    🐛

    Sebastian Späth

    🐛 🤔

    rocky III

    🐛

    Hermann Josef Eckl

    🐛

    Delhaye Cyrille

    🐛 🤔

    João Leandro

    🌍 🤔

    Angelos Chouvardas

    🌍

    Eivind

    🌍

    Ewen

    🌍 🤔

    forght

    🌍

    glottis0q

    🌍

    ButterflyOfFire

    🌍

    Lucian I. Last

    🌍

    LuuzViir

    🌍

    CTHTC

    🌍

    Russian Retro

    🌍

    Marek L'ach

    🌍

    GunChleoc

    🌍

    GabiSnow

    🌍

    bendaha

    🌍

    Samuel Roland

    🌍

    Dimitri Regnier

    🤔

    irithys

    🌍

    Sergi

    🌍

    ghose (XoseM)

    🌍
    - - - - - - -This project follows the -[all-contributors](https://github.com/all-contributors/all-contributors) -specification. Contributions of any kind welcome! - -## Contact - -You may reach us for help or ask any question you have on: - -- [Discord](https://castopod.org/discord) (for direct interaction with - developers and the community) -- [Issue tracker](https://code.castopod.org/adaures/castopod/-/issues) (for - feature requests & bug reports) - -Alternatively, you can follow us on social media platforms to get news about -Castopod: - -- [podlibre.social](https://podlibre.social/@Castopod) (Mastodon instance) -- [Twitter](https://twitter.com/castopod) -- [LinkedIn](https://linkedin.com/company/castopod) -- [Facebook](https://www.facebook.com/castopod) - -## Sponsors - -The ongoing development of Castopod is made possible with the support of its -backers. If you'd like to help, please consider -[sponsoring Castopod's development](https://opencollective.com/castopod/contribute). - -
    - Ad Aures Logo - NLnet Logo -
    - -## License - -[GNU Affero General Public License v3.0](https://choosealicense.com/licenses/agpl-3.0/) - -Copyright © 2020-present, [Ad Aures](https://adaures.com/). -https://img.shields.io/gitlab/v/release/2?color=brightgreen&gitlab_url=https%3A%2F%2Fcode.castopod.org%2F&include_prereleases&label=release -https://img.shields.io/github/license/ad-aures/castopod?color=blue -https://img.shields.io/badge/contributions-welcome-brightgreen.svg -https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg -https://img.shields.io/github/stars/ad-aures/castopod?style=social - -[release]: https://code.castopod.org/adaures/castopod/-/releases -[license]: https://code.castopod.org/adaures/castopod/-/blob/beta/LICENSE.md -[contributions]: https://code.castopod.org/adaures/castopod/-/issues -[semantic-release]: https://github.com/semantic-release/semantic-release -[discord]: https://castopod.org/discord -[stars]: https://github.com/ad-aures/castopod/stargazers -[crowdin]: https://translate.castopod.org/project/castopod diff --git a/docs/src/ca/getting-started/docker.md b/docs/src/ca/getting-started/docker.md deleted file mode 100644 index 7b967d92..00000000 --- a/docs/src/ca/getting-started/docker.md +++ /dev/null @@ -1,150 +0,0 @@ ---- -title: Imatges Docker oficials -sidebarDepth: 3 ---- - -# Imatges Docker oficials - -Castopod envia 2 imatges de Docker al Docker Hub durant el seu procés de creació -automatitzada: - -- [** code>castopod/app**](https://hub.docker.com/r/castopod/app): el - paquet incloent Castopod i totes les dependències -- [**`castopod/web-server`**](https://hub.docker.com/r/castopod/web-server): una - configuració de Nginx per a Castopod - -A més, Castopod requereix una base de dades compatible amb MySQL. Es pot afegir -una base de dades Redis com a gestor de memòria cau. - -## Etiquetes compatibles - -- `develop` [no-estable], darrera versió de la branca de desenvolupament -- `beta` [stable], latest beta version build -- `1.0.0-beta.x` [stable], specific beta version build (since `1.0.0-beta.22`) - -## Exemple d'ús - -1. Instal·leu [docker](https://docs.docker.com/get-docker/) i - [docker-compose](https://docs.docker.com/compose/install/) -2. Creeu un fitxer `docker-compose.yml` amb el següent: - - ```yml - version: "3.7" - - services: - app: - image: castopod/app:beta - container_name: "castopod-app" - volumes: - - castopod-media:/opt/castopod/public/media - environment: - MYSQL_DATABASE: castopod - MYSQL_USER: castopod - MYSQL_PASSWORD: changeme - CP_BASEURL: "http://castopod.example.com" - CP_ANALYTICS_SALT: changeme - CP_CACHE_HANDLER: redis - CP_REDIS_HOST: redis - networks: - - castopod-app - - castopod-db - restart: unless-stopped - - web-server: - image: castopod/web-server:beta - container_name: "castopod-web-server" - volumes: - - castopod-media:/var/www/html/media - networks: - - castopod-app - ports: - - 8080:80 - restart: unless-stopped - - mariadb: - image: mariadb:10.5 - container_name: "castopod-mariadb" - networks: - - castopod-db - volumes: - - castopod-db:/var/lib/mysql - environment: - MYSQL_ROOT_PASSWORD: changeme - MYSQL_DATABASE: castopod - MYSQL_USER: castopod - MYSQL_PASSWORD: changeme - restart: unless-stopped - - redis: - image: redis:7.0-alpine - container_name: "castopod-redis" - volumes: - - castopod-cache:/data - networks: - - castopod-app - - volumes: - castopod-media: - castopod-db: - castopod-cache: - - networks: - castopod-app: - castopod-db: - ``` - - Heu d'adaptar algunes variables a les vostres necessitats (per exemple, - `CP_BASEURL`, `MYSQL_ROOT_PASSWORD`, `MYSQL_PASSWORD` i - `CP_ANALYTICS_SALT`). - -3. Configureu un `reverse proxy` per a TLS (SSL/HTTPS) - - TLS és obligatori perquè ActivityPub funcioni. Aquest feina es pot gestionar - fàcilment amb un `reverse proxy`, per exemple amb - [Caddy](https://caddyserver.com/): - - ``` - #castopod - castopod.exemple.com { - reverse_proxy localhost:8080 - } - ``` - -4. Executeu `docker-compose up -d`, espereu que s'inicialitzi i aneu a - `https://castopod.exemple.com/cp-install` per acabar de configurar Castopod! - -5. Ja esteu a punt, podeu començar a fer podcasts! 🎙️🚀 - -## Variables d'entorn - -- **castopod/app** - - | Nom de la variable | Tipus (`default`) | Default | - | ---------------------------- | ----------------------- | ---------------- | - | **`CP_BASEURL`** | string | `undefined` | - | **`CP_MEDIA_BASEURL`** | ?string | `CP_BASEURL` | - | **`CP_ADMIN_GATEWAY`** | ?string | `"cp-admin"` | - | **`CP_AUTH_GATEWAY`** | ?string | `"cp-auth"` | - | **`CP_ANALYTICS_SALT`** | string | `undefined` | - | **`CP_DATABASE_HOSTNAME`** | ?string | `"mariadb"` | - | **`CP_DATABASE_NAME`** | ?string | `MYSQL_DATABASE` | - | **`CP_DATABASE_USERNAME`** | ?string | `MYSQL_USER` | - | **`CP_DATABASE_PASSWORD`** | ?string | `MYSQL_PASSWORD` | - | **`CP_DATABASE_PREFIX`** | ?string | `"cp_"` | - | **`CP_CACHE_HANDLER`** | [`"file"` or `"redis"`] | `"file"` | - | **`CP_REDIS_HOST`** | ?string | `"localhost"` | - | **`CP_REDIS_PASSWORD`** | ?string | `null` | - | **`CP_REDIS_PORT`** | ?number | `6379` | - | **`CP_REDIS_DATABASE`** | ?number | `0` | - | **`CP_EMAIL_SMTP_HOST`** | ?string | `undefined` | - | **`CP_EMAIL_FROM`** | ?string | `undefined` | - | **`CP_EMAIL_SMTP_USERNAME`** | ?string | `"localhost"` | - | **`CP_EMAIL_SMTP_PASSWORD`** | ?string | `null` | - | **`CP_EMAIL_SMTP_PORT`** | ?number | `25` | - | **`CP_EMAIL_SMTP_CRYPTO`** | [`"tls"` or `"ssl"`] | `"tls"` | - -- **castopod/web-server** - - | Nom de la variable | Type | Default | - | --------------------- | ------- | ------- | - | **`CP_APP_HOSTNAME`** | ?string | `"app"` | diff --git a/docs/src/ca/getting-started/install.md b/docs/src/ca/getting-started/install.md deleted file mode 100644 index 86a47cfd..00000000 --- a/docs/src/ca/getting-started/install.md +++ /dev/null @@ -1,190 +0,0 @@ ---- -title: Instal·lació -sidebarDepth: 3 ---- - -# Com instal·lar Castopod? - -Castopod va ser pensat per ser fàcil d'instal·lar. Ja sigui utilitzant un -allotjament dedicat o un compartit, podeu instal·lar-lo a la majoria de -servidors web compatibles amb PHP-MySQL. - -::: tip Nota - -Hem publicat imatges oficials de Docker per a Castopod! - -Si preferiu utilitzar Docker, podeu ometre això i anar directament a la -[documentació de Docker](./docker.md) per a Castopod. - -::: - -## Requisits - -- PHP v8.0 o superior -- MySQL versió 5.7 o superior o MariaDB versió 10.2 o superior -- Support d'HTTPS - -### PHP v8.0 o superior - -Es requereix PHP versió 8.0 o superior, amb les extensions següents -instal·lades: - -- [intl](https://php.net/manual/en/intl.requirements.php) -- [libcurl](https://php.net/manual/en/curl.requirements.php) -- [mbstring](https://php.net/manual/en/mbstring.installation.php) -- [gd](https://www.php.net/manual/en/image.installation.php) amb les llibreries - **JPEG**, **PNG** i **WEBP**. -- [exif](https://www.php.net/manual/en/exif.installation.php) - -A més, assegureu-vos que les extensions següents estiguin habilitades al vostre -PHP: - -- json (activat per defecte; no el desactiveu) -- xml (activat per defecte; no el desactiveu) -- [mysqlnd](https://php.net/manual/en/mysqlnd.install.php) - -### Base de dades compatible amb MySQL - -> Us recomanem que utilitzeu [MariaDB](https://mariadb.org). - -::: warning Avís - -Castopod només funciona amb bases de dades compatibles amb MySQL 5.7 o superior. -No funcionarà amb l'anterior MySQL v5.6, per exemple, ja que el seu final de -vida va ser el 5 de febrer de 2021. - -::: - -Necessitareu el nom d'amfitrió del servidor (hostname), el nom de la base de -dades, el nom d'usuari i la contrasenya per completar el procés d'instal·lació. -Si no els teniu, poseu-vos en contacte amb l'administrador del vostre servidor. - -#### Privilegis - -User must have at least these privileges on the database for Castopod to work: -`CREATE`, `ALTER`, `DELETE`, `EXECUTE`, `INDEX`, `INSERT`, `SELECT`, `UPDATE`, -`REFERENCES`, `CREATE VIEW`. - -### (Opcional) FFmpeg v4.1.8 o superior per fer videoclips - -Si voleu generar videoclips, cal [FFmpeg](https://www.ffmpeg.org/) versió 4.1.8 -o superior. Cal instal·lar les següents extensions: - -- La llibreria **FreeType 2** per - [gd](https://www.php.net/manual/en/image.installation.php). - -### (Opcional) Altres recomanacions - -- Redis per a un millor rendiment de la memòria cau. -- CDN per a la memòria cau de fitxers estàtics i millors rendiments. -- Passarel·la de correu electrònic per a contrasenyes perdudes. - -## Instruccions d'instal·lació - -### Pre-requisits - -0. Obteniu un servidor web amb els [requisits](#requirements) instal·lats -1. Creeu una base de dades MySQL per a Castopod amb un usuari amb privilegis - d'accés i modificació (per a més informació, vegeu - [base de dades compatible MySQL](#mysql-compatible-database)). -2. Activeu HTTPS al vostre domini amb un _certificat SSL_. -3. Baixeu i descomprimiu el darrer [paquet Castopod](https://castopod.org/) al - servidor web si encara no ho heu fet. - - ⚠️ Establiu l'arrel del document del servidor web a la subcarpeta - `castopod/public/`. -4. Afegiu **tasques cron** al vostre servidor web per a diversos processos en - segon pla (substituïu les rutes d'acord a la vostra configuració de fitxers): - - - Perquè les funcions socials funcionin correctament, aquesta tasca - s'utilitza per transmetre activitats socials als vostres seguidors al - Fediverse: - - ```bash - * * * * * /ruta/al/php /ruta/al/castopod/public/index.php scheduled-activities - ``` - - - Per transmetre els vostres episodis en hubs oberts després de la publicació - mitjançant [WebSub](https://en.wikipedia.org/wiki/WebSub): - - ```bash - * * * * * /ruta/al/php /rutal/al/castopod/public/index.php scheduled-websub-publish - ``` - - - Per crear clips de vídeo (consulteu - [requisits de FFmpeg](#ffmpeg-v418-or-higher-for-video-clips)): - - ```bash - * * * * * /ruta/al/php /ruta/al/castopod/public/index.php scheduled-video-clips - ``` - - > Aquestes tasques s'executen **cada minut**. Podeu configurar la freqüència - > segons les vostres necessitats: cada 5, 10 minuts o més. - -### (recomanat) Assistent d'instal·lació - -1. Executeu l'script d'instal·lació de Castopod anant a la pàgina web de - l'assistent d'instal·lació (`https://exemple.com/cp-install`) al vostre - navegador web preferit. -2. Seguiu les instruccions a la vostra pantalla. -3. Comenceu a fer podcasts! - -::: info Nota - -L'script d'instal·lació escriu un fitxer `.env` a l'arrel del paquet. If you -cannot go through the install wizard, you can create and edit the `.env` file -manually based on the `.env.example` file. - -::: - -### Email/SMTP setup - -Email configuration is required for some features to work properly (eg. -retrieving your forgotten password, sending instructions to premium subscribers, -…) - -You may add your email configuration in your instance's `.env` like so: - -```ini -# […] - -email.fromEmail="your_email_address" -email.SMTPHost="your_smtp_host" -email.SMTPUser="your_smtp_user" -email.SMTPPass="your_smtp_password" -``` - -#### Email config options - -| Variable name | Type | Default | -| ---------------- | -------------------- | ------------ | -| **`fromEmail`** | string | `undefined` | -| **`fromName`** | string | `"Castopod"` | -| **`SMTPHost`** | string | `undefined` | -| **`SMTPUser`** | string | `undefined` | -| **`SMTPPass`** | string | `undefined` | -| **`SMTPPort`** | number | `25` | -| **`SMTPCrypto`** | [`"tls"` or `"ssl"`] | `"tls"` | - -## Paquets de la comunitat - -If you don't want to bother with installing Castopod manually, you may use one -of the packages created and maintained by the open-source community. - -### Install with YunoHost - -[YunoHost](https://yunohost.org/) is a distribution based on Debian GNU/Linux -made up of free and open-source software packages. It manages the hardships of -self-hosting for you. - - diff --git a/docs/src/ca/getting-started/update.md b/docs/src/ca/getting-started/update.md deleted file mode 100644 index cd282372..00000000 --- a/docs/src/ca/getting-started/update.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: Actualitzar -sidebarDepth: 3 ---- - -# Com actualitzar Castopod? - -Després d'instal·lar Castopod, és possible que vulgueu actualitzar la vostra -instància a la darrera versió per gaudir de les últimes funcions ✨, correccions -d'errors 🐛 i millores de rendiment ⚡. - -## Instruccions d'actualització automàtica - -> Aviat... 👀 - -## Instruccions d'actualització manual - -1. Aneu a la - [pàgina de llançaments](https://code.castopod.org/adaures/castopod/-/releases) - i comproveu si la vostra instància està actualitzada amb la darrera versió de - Castopod - - - [On puc trobar la meva versió de Castopod?](#where-can-i-find-my-castopod-version) - -2. Baixeu l'últim paquet de llançament anomenat `Castopod Package`, podeu triar - entre els fitxers `zip` o `tar.gz` - - - ⚠️ Assegureu-vos de descarregar el paquet Castopod i **NO** el codi font - -3. Al vostre servidor: - - - Elimina tots els fitxers excepte `.env` i `public/media` - - Copieu els fitxers nous del paquet descarregat al vostre servidor - - ::: info Nota - - És possible que hàgiu de restablir els permisos dels fitxers durant el - procés d'instal·lació. Comproveu els [Detalls de seguretat](./security.md). - - ::: - -4. Les diferents versions poden incloure instruccions d'actualització - addicionals (vegeu la - [pàgina de versions](https://code.castopod.org/adaures/castopod/-/releases)). - Normalment són scripts de migració de bases de dades en format `.sql` per - actualitzar l'esquema de la base de dades. - - - 👉 Assegureu-vos que executeu els scripts al vostre panell phpmyadmin o - utilitzeu la línia d'ordres per actualitzar la base de dades juntament amb - els fitxers del paquet. - - [Fa molt de temps que no actualitzo la meva instància... Què hauria de fer?](#i-havent-updated-my-instance-in-a-long-time-what-should-i-do) - -5. Si utilitzeu redis, esborreu la memòria cau. -6. ✨ Gaudiu de la vostra nova instància, tot fet i preparat! - -## Preguntes més freqüents (FAQ) - -### On puc trobar la meva versió de Castopod? - -Aneu al vostre panell de control de Castopod, la versió es mostra a la cantonada -inferior esquerra. - -Alternativament, podeu trobar la versió al fitxer -`app > Config > Constants.php`. - -### Fa temps que no actualitzo la meva instància... Què hauria de fer? - -Cap problema. Només heu d'obtenir l'última versió tal com es descriu -anteriorment. Només, quan seguiu les instruccions de la versió en qüestió (4), -realitzeu-les de manera seqüencial, de la més antiga a la més nova. - -> És possible que vulgueu fer una còpia de seguretat de la vostra instància en -> funció del temps que no heu actualitzat Castopod. - -Per exemple, si sou a `v1.0.0-alpha.42` i voleu actualitzar a `v1.0.0-beta.1`: - -0. (molt recomanable) Feu una còpia de seguretat dels vostres fitxers i base de - dades. - -1. Baixeu la darrera versió, sobreescriu els vostres fitxers mantenint `.env` i - `public/media`. - -2. Seguiu les instruccions d'actualització de cada versió seqüencialment (de la - més antiga a la més recent) començant per `v1.0.0-alpha.43`, - `v1.0.0-alpha.44`, `v1.0.0-alpha.45`, ..., `v1.0.0-beta.1`. - -3. ✨ Gaudiu de la vostra nova instància, tot fet i preparat! - -### Hauria de fer una còpia de seguretat abans d'actualitzar? - -T'aconsellem que ho facis, perquè no ho perdis tot si alguna cosa va malament! - -De manera més general, us aconsellem que feu còpies de seguretat periòdiques -dels vostres fitxers i base de dades de Castopod per evitar que ho perdeu tot... diff --git a/docs/src/ca/index.md b/docs/src/ca/index.md deleted file mode 100644 index a2df1fd6..00000000 --- a/docs/src/ca/index.md +++ /dev/null @@ -1,304 +0,0 @@ ---- -sidebarDepth: 2 ---- - -# Benvinguts 👋 - -[![release-badge]][release] [![license-badge]][license] [![contributions-badge]][contributions] [![semantic-release-badge]][semantic-release] [![crowdin-badge]][crowdin] [![discord-badge]][discord] [![stars-badge]][stars] - -Castopod és una plataforma d'allotjament gratuïta i de codi obert creada per a -podcasters que volen involucrar i interactuar amb el seu públic. - -Castopod és fàcil d'instal·lar i s'ha creat amb -[CodeIgniter4](https://codeigniter.com/), un marc PHP potent amb una empremta -molt petita. - -::: info Estat - -Castopod es troba actualment en **beta**, però ja és bastant estable i -l'utilitzen els podcasters d'arreu del món! - -::: - - - -## Característiques - -- 🌱  Gratis i de codi obert (llicència AGPL v3) -- 🔐  Centrat en la sobirania de les dades: el vostre contingut, audiència - i estadístiques us pertanyen, i només a vosaltres -- 🪄  Funcions de podcasting 2.0: GUID, bloqueigos, transcripcions, - finançament, capítols, geo-localització, persones, fragments d'àudio, … -- 💬  Xarxa social integrada: - - 🚀  Castopod forma part de Fediverse, una xarxa social descentralitzada - - ❤️  Creeu publicacions, compartiu-les, afegiu-hi com a preferits i - comenteu episodis -- 📈  Estadístiques integrades: - - ⚖️  Complint amb GDPR / CCPA / LGPD - - 🪙  Mesura d'audiència segons l'estàndard IABv2 - - 🏡  Estadístiques locals, sense cap tercer implicat -- 📢  Eines de màrqueting integrades: - - ✅  Preparada per a SEO (metaetiquetes de gràfics oberts, JSON-LD, …) - - 📱  PWA: instal·lable pels oients com a aplicació autònoma - - 🎨  Colors del tema personalitzables - - 🎬  Genereu videoclips d'episodis preparats per compartir - - 🔉  Genera fragments d'àudio de cada episodi - - ▶️  Reproductor incrustable, per incrustar els episodis a qualsevol - lloc web -- 💸  Monetització: - - 🔗  Enllaços de finançament - - 📲  Anuncis per escoltar-fent-clic - - 🤝  Integració amb value4value i WebMonetization - - 💎  Premium podcasts -- 📡  Publiqueu els vostres episodis a tot arreu amb RSS: - - 📱  A tots els directoris i aplicacions: Podcast Index, Apple Podcasts, - Spotify, Google Podcasts, Deezer, Podcast Addict, Podfriend, … - - ⚡  Emeteu els vostres episodis a l'instant amb WebSub -- 📥  Importació de podcasts: moveu el vostre podcast existent a Castopod -- 📤  Traieu el vostre podcast fora de Castopod -- 🔀  Multi-podcast: allotgeu tants programes com vulgueu en un mateix lloc -- 👥  Multi-usuari: afegiu col·laboradors i definiu rols -- 🌎  Suport i18n: traduït a l'anglès, català, espanyol, xinès, francès, - polonès, alemany, portuguès brasiler ... i - [més per venir](https://translate.castopod.org)! - -## Motivació - -L'ecosistema de podcasting ès descentralitzat per naturalesa: podeu crear el -vostre podcast com a fitxer RSS, publicar-lo a la web i compartir-lo a tot arreu -en línia. - -De fet, és un dels únics mitjans que s'han mantingut durant molt de temps -(dècades!). - -A mesura que els usos evolucionen, cada vegada hi ha més gent que s'incorpora -als podcasts: ja siguin els creadors que troben noves maneres de compartir les -seves idees o els oients a la recerca de millors continguts. - -Essent el podcasting cada cop més utilitzat, algunes empreses intenten -canviar-lo cap a un mitjà més controlat i centralitzat, creant plataformes de -publicació sota el seu control. - -Castopod es va crear amb l'objectiu de proporcionar una alternativa oberta i -sostenible per a que allotjeu els vostres podcasts, promovent la -descentralització i així garantir que la creativitat dels podcasters pugui -expressar-se. - -Aquest projecte és impulsat per la comunitat de codi obert, i concretament pels -moviments [Fediverse](https://fediverse.party/en/fediverse/) i [Podcasting -2.0](https://podcastindex .org/). - -## Comparació amb altres solucions - -Creiem que una mateixa solució no és necessàriament adequada per a tothom, depèn -molt de les vostres necessitats. Per tant, aquí us mostrem comparacions amb -altres eines per ajudar-vos a determinar si Castopod és l'adequat per a -vosaltres. - -### Castopod vs Wordpress - -Casopod es coneix sovint com "el Wordpress per a podcasts" a causa de les -similituds entre els dos. D'alguna manera això és cert. I, de fet, Castopod es -va inspirar molt en l'ecosistema de Wordpress, veient la facilitat d'adopció per -part de la comunitat i el nombre de llocs web que l'executen. - -Igual que Wordpress, Castopod és gratuït i de codi obert, fet amb PHP amb una -base de dades MySQL i està empaquetat de manera que es pot instal·lar fàcilment -a la majoria del servidors web. - -Wordpress és una manera fantàstica de crear el vostre lloc web i ampliar-lo amb -complements per obtenir el que voleu. És un CMS complet que us ajuda a tenir -qualsevol tipus de lloc web en línia. - -D'altra banda, Castopod està pensat per atendre les necessitats dels podcasters -específicament, centrant-se en el podcasting i res més. No necessiteu cap -complement o connector per començar el vostre viatge de podcasting. - -Això permet optimitzar els processos específics del podcasting: des de la -creació dels teus podcasts i la publicació de nous episodis fins a la difusió, -el màrqueting i l'anàlisi estadística. - -Finalment, depenent de les vostres necessitats, Wordpress i Castopod poden -conviure fins i tot, ja que comparteixen els mateixos requisits! - -### Castopod vs Funkwhale - -Funkwhale és un servidor de música de codi obert, modern i auto-allotjat. Igual -que Castopod, Funkwhale es troba al Fediverse, una xarxa social descentralitzada -que permet la interoperabilitat entre ambdues. - -Funkwhale es va construir inicialment al voltant de la música. I més tard, a -mesura que el projecte anava evolucionant, es va introduir la possibilitat -d'allotjar podcasts. - -A diferència de Funkwhale, Castopod ha estat dissenyat i construït exclusivament -al voltant del podcasting. Això permet una implementació més fàcil de funcions -relacionades amb l'ecosistema del podcasting, com ara les funcions del -podcasting 2.0 (transcripcions, capítols, geo-localitzacions, persones, ...). - -Per tant, probablement hauríeu d'utilitzar Funkwhale si voleu allotjar la vostra -música i utilitzar Castopod si voleu allotjar els vostres podcasts. - -### Castopod vs altres plataformes de podcast - -Hi ha moltes solucions per allotjar els vostres podcasts, algunes de les quals -són realment fantàstiques i [moltes](https://podcastindex.org/apps) s'estan -incorporant al carro Podcasting 2.0. com Castopod! - -Cadascuna d'aquestes solucions difereix entre si, podeu comparar-les amb la -[llista de funcions](#features). - -Dit això, hi ha dues diferències principals amb altres solucions de podcasting: - -- Castopod es pot auto-allotjar i és la única solució que us permet mantenir el - control total sobre el que produïu. A més, com que és de codi obert, fins i - tot podeu personalitzar-lo com vulgueu. - -- Castopod és l'única solució que actualment integra tant una xarxa social - descentralitzada amb ActivityPub com moltes de les funcions de podcasting 2.0, - amb l'esperança de salvar la bretxa entre les dues. - -## Col·laborar - -Us agrada Castopod i voleu ajudar? Fes una ullada a la documentació següent per -començar. - -### Codi de conducta - -Castopod ha adoptat un codi de conducta que esperem que els participants del -projecte compleixin. Si us plau, llegiu el -[manual CODE_OF_CONDUCT](https://code.castopod.org/adaures/castopod/-/blob/beta/CODE_OF_CONDUCT.md) -perquè pugueu entendre quines accions seran o no tolerades. - -### Guia de col·laboració - -Llegiu la nostra [guia de col·laboració](./contributing/guidelines.md) per -conèixer el nostre procés de desenvolupament, com proposar correccions d'errors -i millores, i com construir i prova els teus canvis a Castopod. - -## Col·laboradors ✨ - -Gràcies a aquestes persones meravelloses -([clau emoji](https://allcontributors.org/docs/en/emoji-key)): - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Yassine Doghri

    💻 🐛 📖 👀 🚧 🖋 🎨 ️️️️♿️ 🌍 💬 🧑‍🏫 🚇 🤔 📆 📝

    Benjamin Bellamy

    💻 🐛 👀 🖋 🌍 💬 🚇 🤔 📝 📆 📢

    Ola Hneini

    💻 👀 📖 🚧 💬 🤔

    Romain de Laage

    💻 🚇 📖 🌍 🤔

    Lyonel Bernard

    🐛 💬 🔊 🤔

    Christopher Lagonick-Weitzel

    🐛 💬 🔊 🤔

    Ernesto Acosta

    🐛 🔊 🌍 💬 🤔

    Bastien Luneteau

    💻 🐛

    Cécile Ricordeau

    🎨

    Patryk Miś

    🌍

    Marcin Lewandowski

    🐛 🤔

    Sebastian Janik

    💻

    Patryk Karczmarczyk

    💻

    denis d

    🐛 🤔

    Douglas Kastle

    🐛 🤔

    cExplorer

    🐛 🌍

    ImaCrea

    🐛 🤔

    Jonas S

    💻

    LEFEBVRE Yann

    🐛

    Sebastian Späth

    🐛 🤔

    rocky III

    🐛

    Hermann Josef Eckl

    🐛

    Delhaye Cyrille

    🐛 🤔

    João Leandro

    🌍 🤔

    Angelos Chouvardas

    🌍

    Eivind

    🌍

    Ewen

    🌍 🤔

    forght

    🌍

    glottis0q

    🌍

    ButterflyOfFire

    🌍

    Lucian I. Last

    🌍

    LuuzViir

    🌍

    CTHTC

    🌍

    Russian Retro

    🌍

    Marek L'ach

    🌍

    GunChleoc

    🌍

    GabiSnow

    🌍

    bendaha

    🌍

    Samuel Roland

    🌍

    Dimitri Regnier

    🤔

    irithys

    🌍

    Sergi

    🌍

    ghose (XoseM)

    🌍
    - - - - - - -Aquest projecte segueix l'especificació -[all-contributors](https://github.com/all-contributors/all-contributors). -Benvingudes les col·laboracions de qualsevol mena! - -## Contacte - -Podeu contactar amb nosaltres per demanar ajuda o fer qualsevol pregunta que -tingueu via: - -- [Discord](https://castopod.org/discord) (per a la interacció directa amb - desenvolupadors i la comunitat) -- [Seguiment d'incidències](https://code.castopod.org/adaures/castopod/-/issues) - (per a sol·licituds de noves funcionalitats i informes d'errors) - -Alternativament, podeu seguir-nos a les plataformes de xarxes socials per rebre -notícies sobre Castopod: - -- [podlibre.social](https://podlibre.social/@Castopod) (instància de Mastodon) -- [Twitter](https://twitter.com/castopod) -- [LinkedIn](https://linkedin.com/company/castopod) -- [Facebook](https://www.facebook.com/castopod) - -## Patrocinadors - -El desenvolupament continu de Castopod és possible amb el suport dels seus -patrocinadors. Si voleu ajudar, considereu -[patrocinar el desenvolupament de Castopod](https://opencollective.com/castopod/contribute). - -
    - Ad Aures Logo - NLnet Logo -
    - -## Llicència - -[GNU Affero General Public License v3.0](https://choosealicense.com/licenses/agpl-3.0/) - -Copyright © 2020-avui, [Ad Aures](https://adaures.com/). -https://img.shields.io/gitlab/v/release/2?color=brightgreen&gitlab_url=https%3A%2F%2Fcode.castopod.org%2F&include_prereleases&label=release -https://img.shields.io/github/license/ad-aures/castopod?color=blue -https://img.shields.io/badge/contributions-welcome-brightgreen.svg -https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg -https://img.shields.io/github/stars/ad-aures/castopod?style=social - -[release]: https://code.castopod.org/adaures/castopod/-/releases -[license]: https://code.castopod.org/adaures/castopod/-/blob/beta/LICENSE.md -[contributions]: https://code.castopod.org/adaures/castopod/-/issues -[semantic-release]: https://github.com/semantic-release/semantic-release -[discord]: https://castopod.org/discord -[stars]: https://github.com/ad-aures/castopod/stargazers -[crowdin]: https://translate.castopod.org/project/castopod diff --git a/docs/src/components/DocsVersionSelect.astro b/docs/src/components/DocsVersionSelect.astro new file mode 100644 index 00000000..9bedafa9 --- /dev/null +++ b/docs/src/components/DocsVersionSelect.astro @@ -0,0 +1,34 @@ +--- +import Select from '@astrojs/starlight/components/Select.astro'; +--- + + + ' -# honeypot.container = '
    {template}
    ' - -#-------------------------------------------------------------------- -# SECURITY -#-------------------------------------------------------------------- - -# security.csrfProtection = 'cookie' -# security.tokenRandomize = false -# security.tokenName = 'csrf_token_name' -# security.headerName = 'X-CSRF-TOKEN' -# security.cookieName = 'csrf_cookie_name' -# security.expires = 7200 -# security.regenerate = true -# security.redirect = true -# security.samesite = 'Lax' +# session.driver = 'CodeIgniter\Session\Handlers\FileHandler' +# session.savePath = null #-------------------------------------------------------------------- # LOGGER #-------------------------------------------------------------------- # logger.threshold = 4 - -#-------------------------------------------------------------------- -# CURLRequest -#-------------------------------------------------------------------- - -# curlrequest.shareOptions = true diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..c9d330e6 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,23 @@ +import globals from "globals"; +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; +import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; + +export default [ + ...tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.strict, + eslintPluginPrettierRecommended + ), + { + ignores: ["public/*", "docs/*", "vendor/*", "castopod/*"], + }, + { + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + }, + }, + }, +]; diff --git a/modules/Admin/Config/Admin.php b/modules/Admin/Config/Admin.php index 4c289d9c..56741428 100644 --- a/modules/Admin/Config/Admin.php +++ b/modules/Admin/Config/Admin.php @@ -20,5 +20,5 @@ class Admin extends BaseConfig * Number of maximum ffmpeg processes to spawn in parallel when generating video clips. Processes are instance wide, * meaning that they are shared across all podcasts and episodes. */ - public int $videoClipWorkers = 2; + public int $videoClipWorkers = 1; } diff --git a/modules/Admin/Config/Routes.php b/modules/Admin/Config/Routes.php index ad767a0d..375b2943 100644 --- a/modules/Admin/Config/Routes.php +++ b/modules/Admin/Config/Routes.php @@ -4,7 +4,9 @@ declare(strict_types=1); namespace Modules\Admin\Config; -$routes = service('routes'); +use CodeIgniter\Router\RouteCollection; + +/** @var RouteCollection $routes */ // video-clips scheduler $routes->add('scheduled-video-clips', 'SchedulerController::generateVideoClips', [ @@ -24,61 +26,61 @@ $routes->group( ]); $routes->group('settings', static function ($routes): void { $routes->get('/', 'SettingsController', [ - 'as' => 'settings-general', - 'filter' => 'permission:settings-manage', + 'as' => 'settings-general', + 'filter' => 'permission:admin.settings', ]); - $routes->post('instance', 'SettingsController::attemptInstanceEdit', [ - 'as' => 'settings-instance', - 'filter' => 'permission:settings-manage', + $routes->post('instance', 'SettingsController::instanceEditAction', [ + 'as' => 'settings-instance', + 'filter' => 'permission:admin.settings', ]); - $routes->get('instance-delete-icon', 'SettingsController::deleteIcon', [ - 'as' => 'settings-instance-delete-icon', - 'filter' => 'permission:settings-manage', + $routes->get('instance-delete-icon', 'SettingsController::deleteIconAction', [ + 'as' => 'settings-instance-delete-icon', + 'filter' => 'permission:admin.settings', ]); - $routes->post('instance-images-regenerate', 'SettingsController::regenerateImages', [ - 'as' => 'settings-images-regenerate', - 'filter' => 'permission:settings-manage', + $routes->post('instance-images-regenerate', 'SettingsController::regenerateImagesAction', [ + 'as' => 'settings-images-regenerate', + 'filter' => 'permission:admin.settings', ]); - $routes->post('instance-housekeeping-run', 'SettingsController::runHousekeeping', [ - 'as' => 'settings-housekeeping-run', - 'filter' => 'permission:settings-manage', + $routes->post('instance-housekeeping-run', 'SettingsController::housekeepingAction', [ + 'as' => 'settings-housekeeping-run', + 'filter' => 'permission:admin.settings', ]); - $routes->get('theme', 'SettingsController::theme', [ - 'as' => 'settings-theme', - 'filter' => 'permission:settings-manage', + $routes->get('theme', 'SettingsController::themeView', [ + 'as' => 'settings-theme', + 'filter' => 'permission:admin.settings', ]); - $routes->post('theme', 'SettingsController::attemptSetInstanceTheme', [ - 'as' => 'settings-theme', - 'filter' => 'permission:settings-manage', + $routes->post('theme', 'SettingsController::themeAction', [ + 'as' => 'settings-theme', + 'filter' => 'permission:admin.settings', ]); }); $routes->group('persons', static function ($routes): void { - $routes->get('/', 'PersonController', [ - 'as' => 'person-list', - 'filter' => 'permission:person-list', + $routes->get('/', 'PersonController::list', [ + 'as' => 'person-list', + 'filter' => 'permission:persons.manage', ]); - $routes->get('new', 'PersonController::create', [ - 'as' => 'person-create', - 'filter' => 'permission:person-create', + $routes->get('new', 'PersonController::createView', [ + 'as' => 'person-create', + 'filter' => 'permission:persons.manage', ]); - $routes->post('new', 'PersonController::attemptCreate', [ - 'filter' => 'permission:person-create', + $routes->post('new', 'PersonController::createAction', [ + 'filter' => 'permission:persons.manage', ]); $routes->group('(:num)', static function ($routes): void { $routes->get('/', 'PersonController::view/$1', [ - 'as' => 'person-view', - 'filter' => 'permission:person-view', + 'as' => 'person-view', + 'filter' => 'permission:persons.manage', ]); - $routes->get('edit', 'PersonController::edit/$1', [ - 'as' => 'person-edit', - 'filter' => 'permission:person-edit', + $routes->get('edit', 'PersonController::editView/$1', [ + 'as' => 'person-edit', + 'filter' => 'permission:persons.manage', ]); - $routes->post('edit', 'PersonController::attemptEdit/$1', [ - 'filter' => 'permission:person-edit', + $routes->post('edit', 'PersonController::editAction/$1', [ + 'filter' => 'permission:persons.manage', ]); - $routes->add('delete', 'PersonController::delete/$1', [ - 'as' => 'person-delete', - 'filter' => 'permission:person-delete', + $routes->add('delete', 'PersonController::deleteAction/$1', [ + 'as' => 'person-delete', + 'filter' => 'permission:persons.manage', ]); }); }); @@ -87,558 +89,434 @@ $routes->group( $routes->get('/', 'PodcastController::list', [ 'as' => 'podcast-list', ]); - $routes->get('new', 'PodcastController::create', [ - 'as' => 'podcast-create', - 'filter' => 'permission:podcasts-create', + $routes->get('new', 'PodcastController::createView', [ + 'as' => 'podcast-create', + 'filter' => 'permission:podcasts.create', ]); - $routes->post('new', 'PodcastController::attemptCreate', [ - 'filter' => 'permission:podcasts-create', - ]); - $routes->get('import', 'PodcastImportController', [ - 'as' => 'podcast-import', - 'filter' => 'permission:podcasts-import', - ]); - $routes->post('import', 'PodcastImportController::attemptImport', [ - 'filter' => 'permission:podcasts-import', + $routes->post('new', 'PodcastController::createAction', [ + 'filter' => 'permission:podcasts.create', ]); // Podcast // Use ids in admin area to help permission and group lookups $routes->group('(:num)', static function ($routes): void { $routes->get('/', 'PodcastController::view/$1', [ - 'as' => 'podcast-view', - 'filter' => 'permission:podcasts-view,podcast-view', + 'as' => 'podcast-view', + 'filter' => 'permission:podcast$1.view', ]); - $routes->get('edit', 'PodcastController::edit/$1', [ - 'as' => 'podcast-edit', - 'filter' => 'permission:podcast-edit', + $routes->get('edit', 'PodcastController::editView/$1', [ + 'as' => 'podcast-edit', + 'filter' => 'permission:podcast$1.edit', ]); - $routes->post('edit', 'PodcastController::attemptEdit/$1', [ - 'filter' => 'permission:podcast-edit', + $routes->post('edit', 'PodcastController::editAction/$1', [ + 'filter' => 'permission:podcast$1.edit', ]); $routes->get( 'publish', - 'PodcastController::publish/$1', + 'PodcastController::publishView/$1', [ - 'as' => 'podcast-publish', - 'filter' => - 'permission:podcast-manage_publications', + 'as' => 'podcast-publish', + 'filter' => 'permission:podcast$1.manage-publications', ], ); $routes->post( 'publish', - 'PodcastController::attemptPublish/$1', + 'PodcastController::publishAction/$1', [ - 'filter' => - 'permission:podcast-manage_publications', + 'filter' => 'permission:podcast$1.manage-publications', ], ); $routes->get( 'publish-edit', - 'PodcastController::publishEdit/$1', + 'PodcastController::publishEditView/$1', [ - 'as' => 'podcast-publish_edit', - 'filter' => - 'permission:podcast-manage_publications', + 'as' => 'podcast-publish_edit', + 'filter' => 'permission:podcast$1.manage-publications', ], ); $routes->post( 'publish-edit', - 'PodcastController::attemptPublishEdit/$1', + 'PodcastController::publishEditAction/$1', [ - 'filter' => - 'permission:podcast-manage_publications', + 'filter' => 'permission:podcast$1.manage-publications', ], ); $routes->get( 'publish-cancel', - 'PodcastController::publishCancel/$1', + 'PodcastController::publishCancelAction/$1', [ - 'as' => 'podcast-publish-cancel', - 'filter' => - 'permission:podcast-manage_publications', + 'as' => 'podcast-publish-cancel', + 'filter' => 'permission:podcast$1.manage-publications', ], ); - $routes->get('edit/delete-banner', 'PodcastController::deleteBanner/$1', [ - 'as' => 'podcast-banner-delete', - 'filter' => 'permission:podcast-edit', + $routes->get('edit/delete-banner', 'PodcastController::deleteBannerAction/$1', [ + 'as' => 'podcast-banner-delete', + 'filter' => 'permission:podcast$1.edit', ]); - $routes->get('delete', 'PodcastController::delete/$1', [ - 'as' => 'podcast-delete', - 'filter' => 'permission:podcasts-delete', + $routes->get('delete', 'PodcastController::deleteView/$1', [ + 'as' => 'podcast-delete', + 'filter' => 'permission:podcast$1.delete', ]); - $routes->post('delete', 'PodcastController::attemptDelete/$1', [ - 'filter' => 'permission:podcasts-delete', - ]); - $routes->get('update', 'PodcastImportController::updateImport/$1', [ - 'as' => 'podcast-update-feed', - 'filter' => 'permission:podcasts-import', + $routes->post('delete', 'PodcastController::deleteAction/$1', [ + 'filter' => 'permission:podcast$1.delete', ]); $routes->group('persons', static function ($routes): void { - $routes->get('/', 'PodcastPersonController/$1', [ - 'as' => 'podcast-persons-manage', - 'filter' => 'permission:podcast-edit', + $routes->get('/', 'PodcastPersonController::index/$1', [ + 'as' => 'podcast-persons-manage', + 'filter' => 'permission:podcast$1.manage-persons', ]); $routes->post( '/', - 'PodcastPersonController::attemptAdd/$1', + 'PodcastPersonController::createAction/$1', [ - 'filter' => 'permission:podcast-edit', + 'filter' => 'permission:podcast$1.manage-persons', ], ); $routes->get( '(:num)/remove', - 'PodcastPersonController::remove/$1/$2', + 'PodcastPersonController::deleteAction/$1/$2', [ - 'as' => 'podcast-person-remove', - 'filter' => 'permission:podcast-edit', + 'as' => 'podcast-person-remove', + 'filter' => 'permission:podcast$1.manage-persons', ], ); }); $routes->group('analytics', static function ($routes): void { - $routes->get('/', 'PodcastController::viewAnalytics/$1', [ - 'as' => 'podcast-analytics', - 'filter' => 'permission:podcasts-view,podcast-view', + $routes->get('/', 'PodcastController::analyticsView/$1', [ + 'as' => 'podcast-analytics', + 'filter' => 'permission:podcast$1.view', ]); $routes->get( 'webpages', - 'PodcastController::viewAnalyticsWebpages/$1', + 'PodcastController::analyticsWebpagesView/$1', [ - 'as' => 'podcast-analytics-webpages', - 'filter' => 'permission:podcasts-view,podcast-view', + 'as' => 'podcast-analytics-webpages', + 'filter' => 'permission:podcast$1.view', ], ); $routes->get( 'locations', - 'PodcastController::viewAnalyticsLocations/$1', + 'PodcastController::analyticsLocationsView/$1', [ - 'as' => 'podcast-analytics-locations', - 'filter' => 'permission:podcasts-view,podcast-view', + 'as' => 'podcast-analytics-locations', + 'filter' => 'permission:podcast$1.view', ], ); $routes->get( 'unique-listeners', - 'PodcastController::viewAnalyticsUniqueListeners/$1', + 'PodcastController::analyticsUniqueListenersView/$1', [ - 'as' => 'podcast-analytics-unique-listeners', - 'filter' => 'permission:podcasts-view,podcast-view', + 'as' => 'podcast-analytics-unique-listeners', + 'filter' => 'permission:podcast$1.view', ], ); $routes->get( 'listening-time', - 'PodcastController::viewAnalyticsListeningTime/$1', + 'PodcastController::analyticsListeningTimeView/$1', [ - 'as' => 'podcast-analytics-listening-time', - 'filter' => 'permission:podcasts-view,podcast-view', + 'as' => 'podcast-analytics-listening-time', + 'filter' => 'permission:podcast$1.view', ], ); $routes->get( 'time-periods', - 'PodcastController::viewAnalyticsTimePeriods/$1', + 'PodcastController::analyticsTimePeriodsView/$1', [ - 'as' => 'podcast-analytics-time-periods', - 'filter' => 'permission:podcasts-view,podcast-view', + 'as' => 'podcast-analytics-time-periods', + 'filter' => 'permission:podcast$1.view', ], ); $routes->get( 'players', - 'PodcastController::viewAnalyticsPlayers/$1', + 'PodcastController::analyticsPlayersView/$1', [ - 'as' => 'podcast-analytics-players', - 'filter' => 'permission:podcasts-view,podcast-view', + 'as' => 'podcast-analytics-players', + 'filter' => 'permission:podcast$1.view', ], ); }); // Podcast episodes $routes->group('episodes', static function ($routes): void { $routes->get('/', 'EpisodeController::list/$1', [ - 'as' => 'episode-list', - 'filter' => - 'permission:episodes-list,podcast_episodes-list', + 'as' => 'episode-list', + 'filter' => 'permission:podcast$1.episodes.view', ]); - $routes->get('new', 'EpisodeController::create/$1', [ - 'as' => 'episode-create', - 'filter' => 'permission:podcast_episodes-create', + $routes->get('new', 'EpisodeController::createView/$1', [ + 'as' => 'episode-create', + 'filter' => 'permission:podcast$1.episodes.create', ]); $routes->post( 'new', - 'EpisodeController::attemptCreate/$1', + 'EpisodeController::createAction/$1', [ - 'filter' => 'permission:podcast_episodes-create', + 'filter' => 'permission:podcast$1.episodes.create', ], ); // Episode $routes->group('(:num)', static function ($routes): void { $routes->get('/', 'EpisodeController::view/$1/$2', [ - 'as' => 'episode-view', - 'filter' => - 'permission:episodes-view,podcast_episodes-view', + 'as' => 'episode-view', + 'filter' => 'permission:podcast$1.episodes.view', ]); - $routes->get('edit', 'EpisodeController::edit/$1/$2', [ - 'as' => 'episode-edit', - 'filter' => 'permission:podcast_episodes-edit', + $routes->get('edit', 'EpisodeController::editView/$1/$2', [ + 'as' => 'episode-edit', + 'filter' => 'permission:podcast$1.episodes.edit', ]); $routes->post( 'edit', - 'EpisodeController::attemptEdit/$1/$2', + 'EpisodeController::editAction/$1/$2', [ - 'filter' => 'permission:podcast_episodes-edit', + 'filter' => 'permission:podcast$1.episodes.edit', ], ); $routes->get( 'publish', - 'EpisodeController::publish/$1/$2', + 'EpisodeController::publishView/$1/$2', [ - 'as' => 'episode-publish', - 'filter' => - 'permission:podcast-manage_publications', + 'as' => 'episode-publish', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->post( 'publish', - 'EpisodeController::attemptPublish/$1/$2', + 'EpisodeController::publishAction/$1/$2', [ - 'filter' => - 'permission:podcast-manage_publications', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->get( 'publish-edit', - 'EpisodeController::publishEdit/$1/$2', + 'EpisodeController::publishEditView/$1/$2', [ - 'as' => 'episode-publish_edit', - 'filter' => - 'permission:podcast-manage_publications', + 'as' => 'episode-publish_edit', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->post( 'publish-edit', - 'EpisodeController::attemptPublishEdit/$1/$2', + 'EpisodeController::publishEditAction/$1/$2', [ - 'filter' => - 'permission:podcast-manage_publications', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->get( 'publish-cancel', - 'EpisodeController::publishCancel/$1/$2', + 'EpisodeController::publishCancelAction/$1/$2', [ - 'as' => 'episode-publish-cancel', - 'filter' => - 'permission:podcast-manage_publications', + 'as' => 'episode-publish-cancel', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->get( 'publish-date-edit', - 'EpisodeController::publishDateEdit/$1/$2', + 'EpisodeController::publishDateEditView/$1/$2', [ - 'as' => 'episode-publish_date_edit', - 'filter' => - 'permission:podcast-manage_publications', + 'as' => 'episode-publish_date_edit', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->post( 'publish-date-edit', - 'EpisodeController::attemptPublishDateEdit/$1/$2', + 'EpisodeController::publishDateEditAction/$1/$2', [ - 'filter' => - 'permission:podcast-manage_publications', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->get( 'unpublish', - 'EpisodeController::unpublish/$1/$2', + 'EpisodeController::unpublishView/$1/$2', [ - 'as' => 'episode-unpublish', - 'filter' => - 'permission:podcast-manage_publications', + 'as' => 'episode-unpublish', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->post( 'unpublish', - 'EpisodeController::attemptUnpublish/$1/$2', + 'EpisodeController::unpublishAction/$1/$2', [ - 'filter' => - 'permission:podcast-manage_publications', + 'filter' => 'permission:podcast$1.episodes.manage-publications', ], ); $routes->get( 'delete', - 'EpisodeController::delete/$1/$2', + 'EpisodeController::deleteView/$1/$2', [ - 'as' => 'episode-delete', - 'filter' => - 'permission:podcast_episodes-delete', + 'as' => 'episode-delete', + 'filter' => 'permission:podcast$1.episodes.delete', ], ); $routes->post( 'delete', - 'EpisodeController::attemptDelete/$1/$2', + 'EpisodeController::deleteAction/$1/$2', [ - 'filter' => - 'permission:podcast_episodes-delete', + 'filter' => 'permission:podcast$1.episodes.delete', ], ); $routes->get( 'transcript-delete', 'EpisodeController::transcriptDelete/$1/$2', [ - 'as' => 'transcript-delete', - 'filter' => 'permission:podcast_episodes-edit', + 'as' => 'transcript-delete', + 'filter' => 'permission:podcast$1.episodes.edit', ], ); $routes->get( 'chapters-delete', 'EpisodeController::chaptersDelete/$1/$2', [ - 'as' => 'chapters-delete', - 'filter' => 'permission:podcast_episodes-edit', + 'as' => 'chapters-delete', + 'filter' => 'permission:podcast$1.episodes.edit', ], ); $routes->get( 'soundbites', 'SoundbiteController::list/$1/$2', [ - 'as' => 'soundbites-list', - 'filter' => 'permission:podcast_episodes-edit', + 'as' => 'soundbites-list', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->get( 'soundbites/new', - 'SoundbiteController::create/$1/$2', + 'SoundbiteController::createView/$1/$2', [ - 'as' => 'soundbites-create', - 'filter' => 'permission:podcast_episodes-edit', + 'as' => 'soundbites-create', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->post( 'soundbites/new', - 'SoundbiteController::attemptCreate/$1/$2', + 'SoundbiteController::createAction/$1/$2', [ - 'as' => 'soundbites-create', - 'filter' => 'permission:podcast_episodes-edit', + 'as' => 'soundbites-create', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->get( 'soundbites/(:num)/delete', - 'SoundbiteController::delete/$1/$2/$3', + 'SoundbiteController::deleteAction/$1/$2/$3', [ - 'as' => 'soundbites-delete', - 'filter' => 'permission:podcast_episodes-edit', + 'as' => 'soundbites-delete', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->get( 'video-clips', 'VideoClipsController::list/$1/$2', [ - 'as' => 'video-clips-list', - 'filter' => 'permission:podcast_episodes-edit', + 'as' => 'video-clips-list', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->get( 'video-clips/new', - 'VideoClipsController::create/$1/$2', + 'VideoClipsController::createView/$1/$2', [ - 'as' => 'video-clips-create', - 'filter' => 'permission:podcast_episodes-edit', + 'as' => 'video-clips-create', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->post( 'video-clips/new', - 'VideoClipsController::attemptCreate/$1/$2', + 'VideoClipsController::createAction/$1/$2', [ - 'as' => 'video-clips-create', - 'filter' => 'permission:podcast_episodes-edit', + 'as' => 'video-clips-create', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->get( 'video-clips/(:num)', 'VideoClipsController::view/$1/$2/$3', [ - 'as' => 'video-clip', - 'filter' => 'permission:podcast_episodes-edit', + 'as' => 'video-clip', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->get( 'video-clips/(:num)/retry', - 'VideoClipsController::retry/$1/$2/$3', + 'VideoClipsController::retryAction/$1/$2/$3', [ - 'as' => 'video-clip-retry', - 'filter' => 'permission:podcast_episodes-edit', + 'as' => 'video-clip-retry', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->get( 'video-clips/(:num)/delete', - 'VideoClipsController::delete/$1/$2/$3', + 'VideoClipsController::deleteAction/$1/$2/$3', [ - 'as' => 'video-clip-delete', - 'filter' => 'permission:podcast_episodes-edit', + 'as' => 'video-clip-delete', + 'filter' => 'permission:podcast$1.episodes.manage-clips', ], ); $routes->get( 'embed', - 'EpisodeController::embed/$1/$2', + 'EpisodeController::embedView/$1/$2', [ - 'as' => 'embed-add', - 'filter' => 'permission:podcast_episodes-edit', + 'as' => 'embed-add', + 'filter' => 'permission:podcast$1.episodes.edit', ], ); $routes->group('persons', static function ($routes): void { - $routes->get('/', 'EpisodePersonController/$1/$2', [ - 'as' => 'episode-persons-manage', - 'filter' => 'permission:podcast_episodes-edit', + $routes->get('/', 'EpisodePersonController::index/$1/$2', [ + 'as' => 'episode-persons-manage', + 'filter' => 'permission:podcast$1.episodes.manage-persons', ]); $routes->post( '/', - 'EpisodePersonController::attemptAdd/$1/$2', + 'EpisodePersonController::createAction/$1/$2', [ - 'filter' => - 'permission:podcast_episodes-edit', + 'filter' => 'permission:podcast$1.episodes.manage-persons', ], ); $routes->get( '(:num)/remove', - 'EpisodePersonController::remove/$1/$2/$3', + 'EpisodePersonController::deleteAction/$1/$2/$3', [ - 'as' => 'episode-person-remove', - 'filter' => - 'permission:podcast_episodes-edit', + 'as' => 'episode-person-remove', + 'filter' => 'permission:podcast$1.episodes.manage-persons', ], ); }); $routes->group('comments', static function ($routes): void { $routes->post( 'new', - 'EpisodeController::attemptCommentCreate/$1/$2', + 'EpisodeController::commentCreateAction/$1/$2', [ - 'as' => 'comment-attempt-create', - 'filter' => 'permission:podcast-manage_publications', - ] + 'as' => 'comment-attempt-create', + 'filter' => 'permission:podcast$1.episodes.manage-comments', + ], ); $routes->post( '(:uuid)/reply', - 'EpisodeController::attemptCommentReply/$1/$2/$3', + 'EpisodeController::commentReplyAction/$1/$2/$3', [ - 'as' => 'comment-attempt-reply', - 'filter' => 'permission:podcast-manage_publications', - ] + 'as' => 'comment-attempt-reply', + 'filter' => 'permission:podcast$1.episodes.manage-comments', + ], ); $routes->post( 'delete', - 'EpisodeController::attemptCommentDelete/$1/$2', + 'EpisodeController::commentDeleteAction/$1/$2', [ - 'as' => 'comment-attempt-delete', - 'filter' => 'permission:podcast-manage_publications', - ] + 'as' => 'comment-attempt-delete', + 'filter' => 'permission:podcast$1.episodes.manage-comments', + ], ); }); }); }); - // Podcast contributors - $routes->group('contributors', static function ($routes): void { - $routes->get('/', 'ContributorController::list/$1', [ - 'as' => 'contributor-list', - 'filter' => - 'permission:podcasts-view,podcast-manage_contributors', - ]); - $routes->get('add', 'ContributorController::add/$1', [ - 'as' => 'contributor-add', - 'filter' => 'permission:podcast-manage_contributors', - ]); - $routes->post( - 'add', - 'ContributorController::attemptAdd/$1', - [ - 'filter' => - 'permission:podcast-manage_contributors', - ], - ); - // Contributor - $routes->group('(:num)', static function ($routes): void { - $routes->get('/', 'ContributorController::view/$1/$2', [ - 'as' => 'contributor-view', - 'filter' => - 'permission:podcast-manage_contributors', - ]); - $routes->get( - 'edit', - 'ContributorController::edit/$1/$2', - [ - 'as' => 'contributor-edit', - 'filter' => - 'permission:podcast-manage_contributors', - ], - ); - $routes->post( - 'edit', - 'ContributorController::attemptEdit/$1/$2', - [ - 'filter' => - 'permission:podcast-manage_contributors', - ], - ); - $routes->get( - 'remove', - 'ContributorController::remove/$1/$2', - [ - 'as' => 'contributor-remove', - 'filter' => - 'permission:podcast-manage_contributors', - ], - ); - }); - }); - $routes->group('platforms', static function ($routes): void { - $routes->get( - '/', - 'PodcastPlatformController::platforms/$1/podcasting', - [ - 'as' => 'platforms-podcasting', - 'filter' => 'permission:podcast-manage_platforms', - ], - ); - $routes->get( - 'social', - 'PodcastPlatformController::platforms/$1/social', - [ - 'as' => 'platforms-social', - 'filter' => 'permission:podcast-manage_platforms', - ], - ); - $routes->get( - 'funding', - 'PodcastPlatformController::platforms/$1/funding', - [ - 'as' => 'platforms-funding', - 'filter' => 'permission:podcast-manage_platforms', - ], - ); - $routes->post( - 'save/(:platformType)', - 'PodcastPlatformController::attemptPlatformsUpdate/$1/$2', - [ - 'as' => 'platforms-save', - 'filter' => 'permission:podcast-manage_platforms', - ], - ); - $routes->get( - '(:slug)/podcast-platform-remove', - 'PodcastPlatformController::removePodcastPlatform/$1/$2', - [ - 'as' => 'podcast-platform-remove', - 'filter' => 'permission:podcast-manage_platforms', - ], - ); - }); // Podcast notifications $routes->group('notifications', static function ($routes): void { $routes->get('/', 'NotificationController::list/$1', [ - 'as' => 'notification-list', + 'as' => 'notification-list', + 'filter' => 'permission:podcast$1.manage-notifications', ]); - $routes->get('(:num)/mark-as-read', 'NotificationController::markAsRead/$1/$2', [ - 'as' => 'notification-mark-as-read', + $routes->get('(:num)/mark-as-read', 'NotificationController::markAsReadAction/$1/$2', [ + 'as' => 'notification-mark-as-read', + 'filter' => 'permission:podcast$1.manage-notifications', ]); - $routes->get('mark-all-as-read', 'NotificationController::markAllAsRead/$1', [ - 'as' => 'notification-mark-all-as-read', + $routes->get('mark-all-as-read', 'NotificationController::markAllAsReadAction/$1', [ + 'as' => 'notification-mark-all-as-read', + 'filter' => 'permission:podcast$1.manage-notifications', ]); }); }); @@ -650,111 +528,60 @@ $routes->group( ]); $routes->get( 'blocked-actors', - 'FediverseController::blockedActors', + 'FediverseController::blockedActorsView', [ - 'as' => 'fediverse-blocked-actors', - 'filter' => 'permission:fediverse-block_actors', + 'as' => 'fediverse-blocked-actors', + 'filter' => 'permission:fediverse.manage-blocks', ], ); $routes->get( 'blocked-domains', - 'FediverseController::blockedDomains', + 'FediverseController::blockedDomainsView', [ - 'as' => 'fediverse-blocked-domains', - 'filter' => 'permission:fediverse-block_domains', + 'as' => 'fediverse-blocked-domains', + 'filter' => 'permission:fediverse.manage-blocks', ], ); }); // Pages $routes->group('pages', static function ($routes): void { $routes->get('/', 'PageController::list', [ - 'as' => 'page-list', + 'as' => 'page-list', + 'filter' => 'permission:pages.manage', ]); - $routes->get('new', 'PageController::create', [ - 'as' => 'page-create', - 'filter' => 'permission:pages-manage', + $routes->get('new', 'PageController::createView', [ + 'as' => 'page-create', + 'filter' => 'permission:pages.manage', ]); - $routes->post('new', 'PageController::attemptCreate', [ - 'filter' => 'permission:pages-manage', + $routes->post('new', 'PageController::createAction', [ + 'filter' => 'permission:pages.manage', ]); $routes->group('(:num)', static function ($routes): void { $routes->get('/', 'PageController::view/$1', [ 'as' => 'page-view', ]); - $routes->get('edit', 'PageController::edit/$1', [ - 'as' => 'page-edit', - 'filter' => 'permission:pages-manage', + $routes->get('edit', 'PageController::editView/$1', [ + 'as' => 'page-edit', + 'filter' => 'permission:pages.manage', ]); - $routes->post('edit', 'PageController::attemptEdit/$1', [ - 'filter' => 'permission:pages-manage', + $routes->post('edit', 'PageController::editAction/$1', [ + 'filter' => 'permission:pages.manage', ]); - $routes->get('delete', 'PageController::delete/$1', [ - 'as' => 'page-delete', - 'filter' => 'permission:pages-manage', + $routes->get('delete', 'PageController::deleteAction/$1', [ + 'as' => 'page-delete', + 'filter' => 'permission:pages.manage', ]); }); }); - // Users - $routes->group('users', static function ($routes): void { - $routes->get('/', 'UserController::list', [ - 'as' => 'user-list', - 'filter' => 'permission:users-list', - ]); - $routes->get('new', 'UserController::create', [ - 'as' => 'user-create', - 'filter' => 'permission:users-create', - ]); - $routes->post('new', 'UserController::attemptCreate', [ - 'filter' => 'permission:users-create', - ]); - // User - $routes->group('(:num)', static function ($routes): void { - $routes->get('/', 'UserController::view/$1', [ - 'as' => 'user-view', - 'filter' => 'permission:users-view', - ]); - $routes->get('edit', 'UserController::edit/$1', [ - 'as' => 'user-edit', - 'filter' => 'permission:users-manage_authorizations', - ]); - $routes->post('edit', 'UserController::attemptEdit/$1', [ - 'filter' => 'permission:users-manage_authorizations', - ]); - $routes->get('ban', 'UserController::ban/$1', [ - 'as' => 'user-ban', - 'filter' => 'permission:users-manage_bans', - ]); - $routes->get('unban', 'UserController::unBan/$1', [ - 'as' => 'user-unban', - 'filter' => 'permission:users-manage_bans', - ]); - $routes->get( - 'force-pass-reset', - 'UserController::forcePassReset/$1', - [ - 'as' => 'user-force_pass_reset', - 'filter' => 'permission:users-force_pass_reset', - ], - ); - $routes->get('delete', 'UserController::delete/$1', [ - 'as' => 'user-delete', - 'filter' => 'permission:users-delete', - ]); - }); - }); - // My account - $routes->group('my-account', static function ($routes): void { - $routes->get('/', 'MyAccountController', [ - 'as' => 'my-account', - ]); - $routes->get( - 'change-password', - 'MyAccountController::changePassword/$1', - [ - 'as' => 'change-password', - ], - ); - $routes->post('change-password', 'MyAccountController::attemptChange/$1'); - }); + + $routes->get('about', 'AboutController', [ + 'as' => 'admin-about', + 'filter' => 'permission:admin.settings', + ]); + + $routes->post('update', 'AboutController::updateAction', [ + 'as' => 'update', + 'filter' => 'permission:admin.settings', + ]); }, ); diff --git a/modules/Admin/Controllers/AboutController.php b/modules/Admin/Controllers/AboutController.php new file mode 100644 index 00000000..b61abf29 --- /dev/null +++ b/modules/Admin/Controllers/AboutController.php @@ -0,0 +1,53 @@ + current_domain(), + 'version' => CP_VERSION, + 'php_version' => PHP_VERSION, + 'os' => PHP_OS, + 'languages' => implode(', ', config('App')->supportedLocales), + ]; + + $this->setHtmlHead(lang('AboutCastopod.title')); + return view('settings/about', [ + 'info' => $instanceInfo, + ]); + } + + public function updateAction(): RedirectResponse + { + if ($this->request->getPost('action') === 'database') { + return $this->migrateDatabase(); + } + + return redirect()->back() + ->with('error', lang('Security.disallowedAction')); + } + + public function migrateDatabase(): RedirectResponse + { + $migrate = service('migrations'); + + $migrate->setNamespace(null) + ->latest(); + + return redirect()->back() + ->with('message', lang('AboutCastopod.messages.databaseUpdateSuccess')); + } +} diff --git a/modules/Admin/Controllers/BaseController.php b/modules/Admin/Controllers/BaseController.php index 667460a1..1dc5d3bc 100644 --- a/modules/Admin/Controllers/BaseController.php +++ b/modules/Admin/Controllers/BaseController.php @@ -4,36 +4,61 @@ declare(strict_types=1); namespace Modules\Admin\Controllers; +use App\Libraries\HtmlHead; use CodeIgniter\Controller; +use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; +use Override; use Psr\Log\LoggerInterface; use ViewThemes\Theme; /** - * Class BaseController - * * BaseController provides a convenient place for loading components and performing functions that are needed by all - * your controllers. Extend this class in any new controllers: class Home extends BaseController + * your controllers. * - * For security be sure to declare any new methods as protected or private. + * Extend this class in any new controllers: + * ``` + * class Home extends BaseController + * ``` + * + * For security, be sure to declare any new methods as protected or private. */ abstract class BaseController extends Controller { /** - * Constructor. + * Instance of the main Request object. + * + * @var IncomingRequest */ + protected $request; + + #[Override] public function initController( RequestInterface $request, ResponseInterface $response, - LoggerInterface $logger + LoggerInterface $logger, ): void { - $this->helpers = array_merge($this->helpers, ['auth', 'breadcrumb', 'svg', 'components', 'misc']); + // Load here all helpers you want to be available in your controllers that extend BaseController. + // Caution: Do not put the this below the parent::initController() call below. + $this->helpers = [...$this->helpers, 'auth', 'breadcrumb', 'svg', 'components', 'misc']; // Do Not Edit This Line parent::initController($request, $response, $logger); Theme::setTheme('admin'); } + + protected function setHtmlHead(string $title): void + { + /** @var HtmlHead $head */ + $head = service('html_head'); + + $head + ->title($title . ' | Castopod Admin') + ->description( + 'Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience.', + ); + } } diff --git a/modules/Admin/Controllers/ContributorController.php b/modules/Admin/Controllers/ContributorController.php deleted file mode 100644 index 71dfc073..00000000 --- a/modules/Admin/Controllers/ContributorController.php +++ /dev/null @@ -1,203 +0,0 @@ -getPodcastById((int) $params[0])) === null) { - throw PageNotFoundException::forPageNotFound(); - } - - $this->podcast = $podcast; - - if (count($params) <= 1) { - return $this->{$method}(); - } - - if (($this->user = (new UserModel())->getPodcastContributor((int) $params[1], (int) $params[0])) !== null) { - return $this->{$method}(); - } - - throw PageNotFoundException::forPageNotFound(); - } - - public function list(): string - { - $data = [ - 'podcast' => $this->podcast, - ]; - - replace_breadcrumb_params([ - 0 => $this->podcast->title, - ]); - return view('contributor/list', $data); - } - - public function view(): string - { - $data = [ - 'podcast' => $this->podcast, - 'contributor' => (new UserModel())->getPodcastContributor($this->user->id, $this->podcast->id), - ]; - - replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->user->username, - ]); - return view('contributor/view', $data); - } - - public function add(): string - { - helper('form'); - - $users = (new UserModel())->findAll(); - $userOptions = array_reduce( - $users, - static function ($result, $user) { - $result[$user->id] = $user->username; - return $result; - }, - [], - ); - - $roles = (new GroupModel())->getContributorRoles(); - $roleOptions = array_reduce( - $roles, - static function ($result, $role) { - $result[$role->id] = lang('Contributor.roles.' . $role->name); - return $result; - }, - [], - ); - - $data = [ - 'podcast' => $this->podcast, - 'userOptions' => $userOptions, - 'roleOptions' => $roleOptions, - ]; - - replace_breadcrumb_params([ - 0 => $this->podcast->title, - ]); - return view('contributor/add', $data); - } - - public function attemptAdd(): RedirectResponse - { - try { - (new PodcastModel())->addPodcastContributor( - (int) $this->request->getPost('user'), - $this->podcast->id, - (int) $this->request->getPost('role'), - ); - } catch (Exception) { - return redirect() - ->back() - ->withInput() - ->with('errors', [lang('Contributor.messages.alreadyAddedError')]); - } - - return redirect()->route('contributor-list', [$this->podcast->id]); - } - - public function edit(): string - { - helper('form'); - - $roles = (new GroupModel())->getContributorRoles(); - $roleOptions = array_reduce( - $roles, - static function ($result, $role) { - $result[$role->id] = lang('Contributor.roles.' . $role->name); - return $result; - }, - [], - ); - - $data = [ - 'podcast' => $this->podcast, - 'user' => $this->user, - 'contributorGroupId' => (new PodcastModel())->getContributorGroupId( - $this->user->id, - $this->podcast->id, - ), - 'roleOptions' => $roleOptions, - ]; - - replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->user->username, - ]); - return view('contributor/edit', $data); - } - - public function attemptEdit(): RedirectResponse - { - (new PodcastModel())->updatePodcastContributor( - $this->user->id, - $this->podcast->id, - (int) $this->request->getPost('role'), - ); - - return redirect()->route('contributor-edit', [$this->podcast->id, $this->user->id])->with( - 'message', - lang('Contributor.messages.editSuccess') - ); - } - - public function remove(): RedirectResponse - { - if ($this->podcast->created_by === $this->user->id) { - return redirect() - ->back() - ->with('errors', [lang('Contributor.messages.removeOwnerError')]); - } - - $podcastModel = new PodcastModel(); - if ( - ! $podcastModel->removePodcastContributor($this->user->id, $this->podcast->id) - ) { - return redirect() - ->back() - ->with('errors', $podcastModel->errors()); - } - - return redirect() - ->route('contributor-list', [$this->podcast->id]) - ->with( - 'message', - lang('Contributor.messages.removeSuccess', [ - 'username' => $this->user->username, - 'podcastTitle' => $this->podcast->title, - ]), - ); - } -} diff --git a/modules/Admin/Controllers/DashboardController.php b/modules/Admin/Controllers/DashboardController.php index 94cb7e8b..725412a3 100644 --- a/modules/Admin/Controllers/DashboardController.php +++ b/modules/Admin/Controllers/DashboardController.php @@ -11,41 +11,46 @@ declare(strict_types=1); namespace Modules\Admin\Controllers; use App\Models\EpisodeModel; -use App\Models\MediaModel; use App\Models\PodcastModel; use CodeIgniter\I18n\Time; +use Modules\Media\Models\MediaModel; class DashboardController extends BaseController { public function index(): string { $podcastsData = []; - $podcastsCount = (new PodcastModel())->builder() + $podcastsCount = new PodcastModel() + ->builder() ->countAll(); - $podcastsLastPublishedAt = (new PodcastModel())->builder() - ->select('MAX(published_at) as last_published_at') + $podcastsLastPublishedAt = new PodcastModel() + ->builder() + ->selectMax('published_at', 'last_published_at') ->where('`published_at` <= UTC_TIMESTAMP()', null, false) ->get() ->getResultArray()[0]['last_published_at']; $podcastsData['number_of_podcasts'] = (int) $podcastsCount; $podcastsData['last_published_at'] = $podcastsLastPublishedAt === null ? null : new Time( - $podcastsLastPublishedAt + $podcastsLastPublishedAt, ); $episodesData = []; - $episodesCount = (new EpisodeModel())->builder() + $episodesCount = new EpisodeModel() + ->builder() ->countAll(); - $episodesLastPublishedAt = (new EpisodeModel())->builder() - ->select('MAX(published_at) as last_published_at') + $episodesLastPublishedAt = new EpisodeModel() + ->builder() + ->selectMax('published_at', 'last_published_at') ->where('`published_at` <= UTC_TIMESTAMP()', null, false) ->get() ->getResultArray()[0]['last_published_at']; $episodesData['number_of_episodes'] = (int) $episodesCount; $episodesData['last_published_at'] = $episodesLastPublishedAt === null ? null : new Time( - $episodesLastPublishedAt + $episodesLastPublishedAt, ); - $totalUploaded = (new MediaModel())->builder() + $totalUploaded = new MediaModel() + ->builder() ->selectSum('file_size') ->get() ->getResultArray()[0]; @@ -59,24 +64,30 @@ class DashboardController extends BaseController } $storageData = [ - 'limit' => formatBytes((int) $storageLimitBytes), - 'percentage' => round((((int) $totalUploaded['file_size']) / $storageLimitBytes) * 100, 0), + 'limit' => formatBytes((int) $storageLimitBytes), + 'percentage' => round((((int) $totalUploaded['file_size']) / $storageLimitBytes) * 100, 0), 'total_uploaded' => formatBytes((int) $totalUploaded['file_size']), ]; $onlyPodcastId = null; if ($podcastsData['number_of_podcasts'] === 1) { - $onlyPodcastId = (new PodcastModel())->first() + $onlyPodcastId = new PodcastModel() + ->first() ->id; } + $bandwidthLimit = config('App') + ->bandwidthLimit; + $data = [ - 'podcastsData' => $podcastsData, - 'episodesData' => $episodesData, - 'storageData' => $storageData, - 'onlyPodcastId' => $onlyPodcastId, + 'podcastsData' => $podcastsData, + 'episodesData' => $episodesData, + 'storageData' => $storageData, + 'bandwidthLimit' => $bandwidthLimit === null ? null : formatBytes($bandwidthLimit * 1000000000), + 'onlyPodcastId' => $onlyPodcastId, ]; + $this->setHtmlHead(lang('Dashboard.home')); return view('dashboard', $data); } } diff --git a/modules/Admin/Controllers/EpisodeController.php b/modules/Admin/Controllers/EpisodeController.php index 6c14d7f3..7319afd6 100644 --- a/modules/Admin/Controllers/EpisodeController.php +++ b/modules/Admin/Controllers/EpisodeController.php @@ -17,12 +17,15 @@ use App\Entities\Podcast; use App\Entities\Post; use App\Models\EpisodeCommentModel; use App\Models\EpisodeModel; -use App\Models\MediaModel; use App\Models\PodcastModel; use App\Models\PostModel; use CodeIgniter\Exceptions\PageNotFoundException; +use CodeIgniter\HTTP\Files\UploadedFile; use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\I18n\Time; +use Modules\Media\Entities\Chapters; +use Modules\Media\Entities\Transcript; +use Modules\Media\Models\MediaModel; class EpisodeController extends BaseController { @@ -32,116 +35,126 @@ class EpisodeController extends BaseController public function _remap(string $method, string ...$params): mixed { + if ($params === []) { + throw PageNotFoundException::forPageNotFound(); + } + + if (count($params) === 1) { + if (! ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast) { + throw PageNotFoundException::forPageNotFound(); + } + + return $this->{$method}($podcast); + } + if ( - ($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) === null + ! ($episode = new EpisodeModel()->getEpisodeById((int) $params[1])) instanceof Episode ) { throw PageNotFoundException::forPageNotFound(); } - $this->podcast = $podcast; + unset($params[0]); + unset($params[1]); - if (count($params) > 1) { - if ( - ! ($episode = (new EpisodeModel()) - ->where([ - 'id' => $params[1], - 'podcast_id' => $params[0], - ]) - ->first()) - ) { - throw PageNotFoundException::forPageNotFound(); - } - - $this->episode = $episode; - - unset($params[1]); - unset($params[0]); - } - - return $this->{$method}(...$params); + return $this->{$method}($episode, ...$params); } - public function list(): string + public function list(Podcast $podcast): string { /** @var ?string $query */ $query = $this->request->getGet('q'); + $episodeModel = new EpisodeModel(); if ($query !== null && $query !== '') { // Default value for MySQL Full-Text Search's minimum length of words is 4. // Use LIKE operator as a fallback. if (strlen($query) < 4) { - $episodes = (new EpisodeModel()) - ->where('podcast_id', $this->podcast->id) - ->like('title', $query) - ->orLike('description_markdown', $query) + $episodes = $episodeModel + ->where('podcast_id', $podcast->id) + ->like('title', $episodeModel->db->escapeLikeString($query)) + ->orLike('description_markdown', $episodeModel->db->escapeLikeString($query)) + ->orLike('slug', $episodeModel->db->escapeLikeString($query)) + ->orLike('location_name', $episodeModel->db->escapeLikeString($query)) + ->orderBy('-`published_at`', '', false) ->orderBy('created_at', 'desc'); } else { - $episodes = (new EpisodeModel()) - ->where('podcast_id', $this->podcast->id) - ->where("MATCH (title, description_markdown) AGAINST ('{$query}')"); + $episodes = $episodeModel + ->where('podcast_id', $podcast->id) + ->where( + "MATCH (title, description_markdown, slug, location_name) AGAINST ('{$episodeModel->db->escapeString( + $query, + )}')", + ); } } else { - $episodes = (new EpisodeModel()) - ->where('podcast_id', $this->podcast->id) + $episodes = $episodeModel + ->where('podcast_id', $podcast->id) + ->orderBy('-`published_at`', '', false) ->orderBy('created_at', 'desc'); } helper('form'); $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, 'episodes' => $episodes->paginate(10), - 'pager' => $episodes->pager, - 'query' => $query, + 'pager' => $episodes->pager, + 'query' => $query, ]; + $this->setHtmlHead(lang('Episode.all_podcast_episodes')); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $podcast->at_handle, ]); return view('episode/list', $data); } - public function view(): string + public function view(Episode $episode): string { $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, ]; + $this->setHtmlHead($episode->title); replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/view', $data); } - public function create(): string + public function createView(Podcast $podcast): string { helper(['form']); - $currentSeasonNumber = (new EpisodeModel())->getCurrentSeasonNumber($this->podcast->id); + $currentSeasonNumber = new EpisodeModel() + ->getCurrentSeasonNumber($podcast->id); $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, 'currentSeasonNumber' => $currentSeasonNumber, - 'nextEpisodeNumber' => (new EpisodeModel())->getNextEpisodeNumber($this->podcast->id, $currentSeasonNumber), + 'nextEpisodeNumber' => new EpisodeModel() + ->getNextEpisodeNumber($podcast->id, $currentSeasonNumber), ]; + + $this->setHtmlHead(lang('Episode.create')); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $podcast->at_handle, ]); return view('episode/create', $data); } - public function attemptCreate(): RedirectResponse + public function createAction(Podcast $podcast): RedirectResponse { $rules = [ - 'audio_file' => 'uploaded[audio_file]|ext_in[audio_file,mp3,m4a]', - 'cover' => - 'is_image[cover]|ext_in[cover,jpg,png]|min_dims[cover,1400,1400]|is_image_ratio[cover,1,1]', - 'transcript_file' => - 'ext_in[transcript,srt]|permit_empty', - 'chapters_file' => 'ext_in[chapters,json]|permit_empty', + 'title' => 'required', + 'slug' => 'required|max_length[128]', + 'audio_file' => 'uploaded[audio_file]|ext_in[audio_file,mp3,m4a]', + 'cover' => 'is_image[cover]|ext_in[cover,jpg,jpeg,png]|min_dims[cover,1400,1400]|is_image_ratio[cover,1,1]', + 'transcript_file' => 'ext_in[transcript_file,srt,vtt]', + 'chapters_file' => 'ext_in[chapters_file,json]|is_json[chapters_file]', ]; - if ($this->podcast->type === 'serial') { + if ($podcast->type === 'serial' && $this->request->getPost('type') === 'full') { $rules['episode_number'] = 'required'; } @@ -152,36 +165,36 @@ class EpisodeController extends BaseController ->with('errors', $this->validator->getErrors()); } - if ((new EpisodeModel()) + $validData = $this->validator->getValidated(); + + if (new EpisodeModel() ->where([ - 'slug' => $this->request->getPost('slug'), - 'podcast_id' => $this->podcast->id, + 'slug' => $validData['slug'], + 'podcast_id' => $podcast->id, ]) - ->first()) { + ->first() instanceof Episode) { return redirect() ->back() ->withInput() ->with('error', lang('Episode.messages.sameSlugError')); } - $db = db_connect(); - $db->transStart(); - $newEpisode = new Episode([ - 'podcast_id' => $this->podcast->id, - 'title' => $this->request->getPost('title'), - 'slug' => $this->request->getPost('slug'), - 'guid' => null, - 'audio' => $this->request->getFile('audio_file'), - 'cover' => $this->request->getFile('cover'), + 'created_by' => user_id(), + 'updated_by' => user_id(), + 'podcast_id' => $podcast->id, + 'title' => $this->request->getPost('title'), + 'slug' => $this->request->getPost('slug'), + 'guid' => null, + 'audio' => $this->request->getFile('audio_file'), + 'cover' => $this->request->getFile('cover'), 'description_markdown' => $this->request->getPost('description'), - 'location' => $this->request->getPost('location_name') === '' ? null : new Location($this->request->getPost( - 'location_name' - )), - 'transcript' => $this->request->getFile('transcript'), - 'chapters' => $this->request->getFile('chapters'), - 'parental_advisory' => - $this->request->getPost('parental_advisory') !== 'undefined' + 'location' => $this->request->getPost('location_name') === '' ? null : new Location( + $this->request->getPost('location_name'), + ), + 'transcript' => $this->request->getFile('transcript'), + 'chapters' => $this->request->getFile('chapters'), + 'parental_advisory' => $this->request->getPost('parental_advisory') !== 'undefined' ? $this->request->getPost('parental_advisory') : null, 'number' => $this->request->getPost('episode_number') @@ -190,12 +203,9 @@ class EpisodeController extends BaseController 'season_number' => $this->request->getPost('season_number') ? (int) $this->request->getPost('season_number') : null, - 'type' => $this->request->getPost('type'), - 'is_blocked' => $this->request->getPost('block') === 'yes', - 'custom_rss_string' => $this->request->getPost('custom_rss'), - 'is_premium' => $this->request->getPost('premium') === 'yes', - 'created_by' => user_id(), - 'updated_by' => user_id(), + 'type' => $this->request->getPost('type'), + 'is_blocked' => $this->request->getPost('block') === 'yes', + 'is_premium' => $this->request->getPost('premium') === 'yes', 'published_at' => null, ]); @@ -204,7 +214,7 @@ class EpisodeController extends BaseController $newEpisode->setTranscript($this->request->getFile('transcript_file')); } elseif ($transcriptChoice === 'remote-url') { $newEpisode->transcript_remote_url = $this->request->getPost( - 'transcript_remote_url' + 'transcript_remote_url', ) === '' ? null : $this->request->getPost('transcript_remote_url'); } @@ -213,73 +223,53 @@ class EpisodeController extends BaseController $newEpisode->setChapters($this->request->getFile('chapters_file')); } elseif ($chaptersChoice === 'remote-url') { $newEpisode->chapters_remote_url = $this->request->getPost( - 'chapters_remote_url' + 'chapters_remote_url', ) === '' ? null : $this->request->getPost('chapters_remote_url'); } $episodeModel = new EpisodeModel(); if (! ($newEpisodeId = $episodeModel->insert($newEpisode, true))) { - $db->transRollback(); return redirect() ->back() ->withInput() ->with('errors', $episodeModel->errors()); } - // update podcast's episode_description_footer_markdown if changed - $this->podcast->episode_description_footer_markdown = $this->request->getPost( - 'description_footer' - ) === '' ? null : $this->request->getPost('description_footer'); - - if ($this->podcast->hasChanged('episode_description_footer_markdown')) { - $podcastModel = new PodcastModel(); - - if (! $podcastModel->update($this->podcast->id, $this->podcast)) { - $db->transRollback(); - return redirect() - ->back() - ->withInput() - ->with('errors', $podcastModel->errors()); - } - } - - $db->transComplete(); - - return redirect()->route('episode-view', [$this->podcast->id, $newEpisodeId])->with( + return redirect()->route('episode-view', [$podcast->id, $newEpisodeId])->with( 'message', - lang('Episode.messages.createSuccess') + lang('Episode.messages.createSuccess'), ); } - public function edit(): string + public function editView(Episode $episode): string { helper(['form']); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, ]; + $this->setHtmlHead(lang('Episode.edit')); replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/edit', $data); } - public function attemptEdit(): RedirectResponse + public function editAction(Episode $episode): RedirectResponse { $rules = [ - 'audio_file' => - 'uploaded[audio_file]|ext_in[audio_file,mp3,m4a]|permit_empty', - 'cover' => - 'is_image[cover]|ext_in[cover,jpg,png]|min_dims[cover,1400,1400]|is_image_ratio[cover,1,1]', - 'transcript_file' => - 'ext_in[transcript_file,txt,html,srt,json]|permit_empty', - 'chapters_file' => 'ext_in[chapters_file,json]|permit_empty', + 'title' => 'required', + 'slug' => 'required|max_length[128]', + 'audio_file' => 'ext_in[audio_file,mp3,m4a]', + 'cover' => 'is_image[cover]|ext_in[cover,jpg,jpeg,png]|min_dims[cover,1400,1400]|is_image_ratio[cover,1,1]', + 'transcript_file' => 'ext_in[transcript_file,srt,vtt]', + 'chapters_file' => 'ext_in[chapters_file,json]|is_json[chapters_file]', ]; - if ($this->podcast->type === 'serial') { + if ($episode->podcast->type === 'serial' && $this->request->getPost('type') === 'full') { $rules['episode_number'] = 'required'; } @@ -290,117 +280,91 @@ class EpisodeController extends BaseController ->with('errors', $this->validator->getErrors()); } - $this->episode->title = $this->request->getPost('title'); - $this->episode->slug = $this->request->getPost('slug'); - $this->episode->description_markdown = $this->request->getPost('description'); - $this->episode->location = $this->request->getPost('location_name') === '' ? null : new Location( - $this->request->getPost('location_name') + $validData = $this->validator->getValidated(); + + $episode->title = $this->request->getPost('title'); + $episode->slug = $validData['slug']; + $episode->description_markdown = $this->request->getPost('description'); + $episode->location = $this->request->getPost('location_name') === '' ? null : new Location( + $this->request->getPost('location_name'), ); - $this->episode->parental_advisory = + $episode->parental_advisory = $this->request->getPost('parental_advisory') !== 'undefined' ? $this->request->getPost('parental_advisory') : null; - $this->episode->number = $this->request->getPost('episode_number') - ? $this->request->getPost('episode_number') - : null; - $this->episode->season_number = $this->request->getPost('season_number') - ? $this->request->getPost('season_number') - : null; - $this->episode->type = $this->request->getPost('type'); - $this->episode->is_blocked = $this->request->getPost('block') === 'yes'; - $this->episode->custom_rss_string = $this->request->getPost('custom_rss'); - $this->episode->is_premium = $this->request->getPost('premium') === 'yes'; + $episode->number = $this->request->getPost('episode_number') ?: null; + $episode->season_number = $this->request->getPost('season_number') ?: null; + $episode->type = $this->request->getPost('type'); + $episode->is_blocked = $this->request->getPost('block') === 'yes'; + $episode->is_premium = $this->request->getPost('premium') === 'yes'; - $this->episode->updated_by = (int) user_id(); - $this->episode->setAudio($this->request->getFile('audio_file')); - $this->episode->setCover($this->request->getFile('cover')); + $episode->updated_by = (int) user_id(); + $episode->setAudio($this->request->getFile('audio_file')); + $episode->setCover($this->request->getFile('cover')); // republish on websub hubs upon edit - $this->episode->is_published_on_hubs = false; + $episode->is_published_on_hubs = false; $transcriptChoice = $this->request->getPost('transcript-choice'); if ($transcriptChoice === 'upload-file') { $transcriptFile = $this->request->getFile('transcript_file'); - if ($transcriptFile !== null && $transcriptFile->isValid()) { - $this->episode->setTranscript($transcriptFile); - $this->episode->transcript_remote_url = null; + if ($transcriptFile instanceof UploadedFile && $transcriptFile->isValid()) { + $episode->setTranscript($transcriptFile); + $episode->transcript_remote_url = null; } } elseif ($transcriptChoice === 'remote-url') { if ( ($transcriptRemoteUrl = $this->request->getPost('transcript_remote_url')) && - (($transcriptFile = $this->episode->transcript_id) !== null) + (($transcriptFile = $episode->transcript_id) !== null) ) { - (new MediaModel())->deleteMedia($this->episode->transcript); + new MediaModel() + ->deleteMedia($episode->transcript); } - $this->episode->transcript_remote_url = $transcriptRemoteUrl === '' ? null : $transcriptRemoteUrl; + $episode->transcript_remote_url = $transcriptRemoteUrl === '' ? null : $transcriptRemoteUrl; } $chaptersChoice = $this->request->getPost('chapters-choice'); if ($chaptersChoice === 'upload-file') { $chaptersFile = $this->request->getFile('chapters_file'); - if ($chaptersFile !== null && $chaptersFile->isValid()) { - $this->episode->setChapters($chaptersFile); - $this->episode->chapters_remote_url = null; + if ($chaptersFile instanceof UploadedFile && $chaptersFile->isValid()) { + $episode->setChapters($chaptersFile); + $episode->chapters_remote_url = null; } } elseif ($chaptersChoice === 'remote-url') { if ( ($chaptersRemoteUrl = $this->request->getPost('chapters_remote_url')) && - (($chaptersFile = $this->episode->chapters) !== null) + (($chaptersFile = $episode->chapters) instanceof Chapters) ) { - (new MediaModel())->deleteMedia($this->episode->chapters); + new MediaModel() + ->deleteMedia($episode->chapters); } - $this->episode->chapters_remote_url = $chaptersRemoteUrl === '' ? null : $chaptersRemoteUrl; + $episode->chapters_remote_url = $chaptersRemoteUrl === '' ? null : $chaptersRemoteUrl; } - $db = db_connect(); - $db->transStart(); - $episodeModel = new EpisodeModel(); - - if (! $episodeModel->update($this->episode->id, $this->episode)) { - $db->transRollback(); - + if (! $episodeModel->update($episode->id, $episode)) { return redirect() ->back() ->withInput() ->with('errors', $episodeModel->errors()); } - // update podcast's episode_description_footer_markdown if changed - $this->podcast->episode_description_footer_markdown = $this->request->getPost( - 'description_footer' - ) === '' ? null : $this->request->getPost('description_footer'); - - if ($this->podcast->hasChanged('episode_description_footer_markdown')) { - $podcastModel = new PodcastModel(); - if (! $podcastModel->update($this->podcast->id, $this->podcast)) { - $db->transRollback(); - - return redirect() - ->back() - ->withInput() - ->with('errors', $podcastModel->errors()); - } - } - - $db->transComplete(); - - return redirect()->route('episode-edit', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('episode-edit', [$episode->podcast_id, $episode->id])->with( 'message', - lang('Episode.messages.editSuccess') + lang('Episode.messages.editSuccess'), ); } - public function transcriptDelete(): RedirectResponse + public function transcriptDelete(Episode $episode): RedirectResponse { - if ($this->episode->transcript === null) { + if (! $episode->transcript instanceof Transcript) { return redirect()->back(); } $mediaModel = new MediaModel(); - if (! $mediaModel->deleteMedia($this->episode->transcript)) { + if (! $mediaModel->deleteMedia($episode->transcript)) { return redirect() ->back() ->withInput() @@ -410,14 +374,14 @@ class EpisodeController extends BaseController return redirect()->back(); } - public function chaptersDelete(): RedirectResponse + public function chaptersDelete(Episode $episode): RedirectResponse { - if ($this->episode->chapters === null) { + if (! $episode->chapters instanceof Chapters) { return redirect()->back(); } $mediaModel = new MediaModel(); - if (! $mediaModel->deleteMedia($this->episode->chapters)) { + if (! $mediaModel->deleteMedia($episode->chapters)) { return redirect() ->back() ->withInput() @@ -427,36 +391,36 @@ class EpisodeController extends BaseController return redirect()->back(); } - public function publish(): string | RedirectResponse + public function publishView(Episode $episode): string | RedirectResponse { - if ($this->episode->publication_status === 'not_published') { + if ($episode->publication_status === 'not_published') { helper(['form']); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, ]; + $this->setHtmlHead(lang('Episode.publish')); replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/publish', $data); } - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with( 'error', - lang('Episode.publish_error') + lang('Episode.publish_error'), ); } - public function attemptPublish(): RedirectResponse + public function publishAction(Episode $episode): RedirectResponse { - if ($this->podcast->publication_status === 'published') { + if ($episode->podcast->publication_status === 'published') { $rules = [ - 'publication_method' => 'required', - 'scheduled_publication_date' => - 'valid_date[Y-m-d H:i]|permit_empty', + 'publication_method' => 'required', + 'scheduled_publication_date' => 'valid_date[Y-m-d H:i]|permit_empty', ]; if (! $this->validate($rules)) { @@ -471,18 +435,18 @@ class EpisodeController extends BaseController $db->transStart(); $newPost = new Post([ - 'actor_id' => $this->podcast->actor_id, - 'episode_id' => $this->episode->id, - 'message' => $this->request->getPost('message'), + 'actor_id' => $episode->podcast->actor_id, + 'episode_id' => $episode->id, + 'message' => $this->request->getPost('message'), 'created_by' => user_id(), ]); - if ($this->podcast->publication_status === 'published') { + if ($episode->podcast->publication_status === 'published') { $publishMethod = $this->request->getPost('publication_method'); if ($publishMethod === 'schedule') { $scheduledPublicationDate = $this->request->getPost('scheduled_publication_date'); if ($scheduledPublicationDate) { - $this->episode->published_at = Time::createFromFormat( + $episode->published_at = Time::createFromFormat( 'Y-m-d H:i', $scheduledPublicationDate, $this->request->getPost('client_timezone'), @@ -495,16 +459,16 @@ class EpisodeController extends BaseController ->with('error', lang('Episode.messages.scheduleDateError')); } } else { - $this->episode->published_at = Time::now(); + $episode->published_at = Time::now(); } - } elseif ($this->podcast->publication_status === 'scheduled') { + } elseif ($episode->podcast->publication_status === 'scheduled') { // podcast publication date has already been set - $this->episode->published_at = $this->podcast->published_at->addSeconds(1); + $episode->published_at = $episode->podcast->published_at->addSeconds(1); } else { - $this->episode->published_at = Time::now(); + $episode->published_at = Time::now(); } - $newPost->published_at = $this->episode->published_at; + $newPost->published_at = $episode->published_at; $postModel = new PostModel(); if (! $postModel->addPost($newPost)) { @@ -516,7 +480,7 @@ class EpisodeController extends BaseController } $episodeModel = new EpisodeModel(); - if (! $episodeModel->update($this->episode->id, $this->episode)) { + if (! $episodeModel->update($episode->id, $episode)) { $db->transRollback(); return redirect() ->back() @@ -526,51 +490,51 @@ class EpisodeController extends BaseController $db->transComplete(); - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with( 'message', lang('Episode.messages.publishSuccess', [ - 'publication_status' => $this->episode->publication_status, - ]) + 'publication_status' => $episode->publication_status, + ]), ); } - public function publishEdit(): string | RedirectResponse + public function publishEditView(Episode $episode): string | RedirectResponse { - if (in_array($this->episode->publication_status, ['scheduled', 'with_podcast'], true)) { + if (in_array($episode->publication_status, ['scheduled', 'with_podcast'], true)) { helper(['form']); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, - 'post' => (new PostModel()) + 'podcast' => $episode->podcast, + 'episode' => $episode, + 'post' => new PostModel() ->where([ - 'actor_id' => $this->podcast->actor_id, - 'episode_id' => $this->episode->id, + 'actor_id' => $episode->podcast->actor_id, + 'episode_id' => $episode->id, ]) ->first(), ]; + $this->setHtmlHead(lang('Episode.publish_edit')); replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/publish_edit', $data); } - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with( 'error', - lang('Episode.publish_edit_error') + lang('Episode.publish_edit_error'), ); } - public function attemptPublishEdit(): RedirectResponse + public function publishEditAction(Episode $episode): RedirectResponse { - if ($this->podcast->publication_status === 'published') { + if ($episode->podcast->publication_status === 'published') { $rules = [ - 'post_id' => 'required', - 'publication_method' => 'required', - 'scheduled_publication_date' => - 'valid_date[Y-m-d H:i]|permit_empty', + 'post_id' => 'required', + 'publication_method' => 'required', + 'scheduled_publication_date' => 'valid_date[Y-m-d H:i]|permit_empty', ]; if (! $this->validate($rules)) { @@ -584,12 +548,12 @@ class EpisodeController extends BaseController $db = db_connect(); $db->transStart(); - if ($this->podcast->publication_status === 'published') { + if ($episode->podcast->publication_status === 'published') { $publishMethod = $this->request->getPost('publication_method'); if ($publishMethod === 'schedule') { $scheduledPublicationDate = $this->request->getPost('scheduled_publication_date'); if ($scheduledPublicationDate) { - $this->episode->published_at = Time::createFromFormat( + $episode->published_at = Time::createFromFormat( 'Y-m-d H:i', $scheduledPublicationDate, $this->request->getPost('client_timezone'), @@ -602,20 +566,21 @@ class EpisodeController extends BaseController ->with('error', lang('Episode.messages.scheduleDateError')); } } else { - $this->episode->published_at = Time::now(); + $episode->published_at = Time::now(); } - } elseif ($this->podcast->publication_status === 'scheduled') { + } elseif ($episode->podcast->publication_status === 'scheduled') { // podcast publication date has already been set - $this->episode->published_at = $this->podcast->published_at->addSeconds(1); + $episode->published_at = $episode->podcast->published_at->addSeconds(1); } else { - $this->episode->published_at = Time::now(); + $episode->published_at = Time::now(); } - $post = (new PostModel())->getPostById($this->request->getPost('post_id')); + $post = new PostModel() + ->getPostById($this->request->getPost('post_id')); - if ($post !== null) { + if ($post instanceof Post) { $post->message = $this->request->getPost('message'); - $post->published_at = $this->episode->published_at; + $post->published_at = $episode->published_at; $postModel = new PostModel(); if (! $postModel->editPost($post)) { @@ -628,7 +593,7 @@ class EpisodeController extends BaseController } $episodeModel = new EpisodeModel(); - if (! $episodeModel->update($this->episode->id, $this->episode)) { + if (! $episodeModel->update($episode->id, $episode)) { $db->transRollback(); return redirect() ->back() @@ -638,33 +603,33 @@ class EpisodeController extends BaseController $db->transComplete(); - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with( 'message', lang('Episode.messages.publishSuccess', [ - 'publication_status' => $this->episode->publication_status, - ]) + 'publication_status' => $episode->publication_status, + ]), ); } - public function publishCancel(): RedirectResponse + public function publishCancelAction(Episode $episode): RedirectResponse { - if (in_array($this->episode->publication_status, ['scheduled', 'with_podcast'], true)) { + if (in_array($episode->publication_status, ['scheduled', 'with_podcast'], true)) { $db = db_connect(); $db->transStart(); $postModel = new PostModel(); $post = $postModel ->where([ - 'actor_id' => $this->podcast->actor_id, - 'episode_id' => $this->episode->id, + 'actor_id' => $episode->podcast->actor_id, + 'episode_id' => $episode->id, ]) ->first(); $postModel->removePost($post); - $this->episode->published_at = null; + $episode->published_at = null; $episodeModel = new EpisodeModel(); - if (! $episodeModel->update($this->episode->id, $this->episode)) { + if (! $episodeModel->update($episode->id, $episode)) { $db->transRollback(); return redirect() ->back() @@ -674,37 +639,37 @@ class EpisodeController extends BaseController $db->transComplete(); - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with( 'message', - lang('Episode.messages.publishCancelSuccess') + lang('Episode.messages.publishCancelSuccess'), ); } - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]); + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id]); } - public function publishDateEdit(): string|RedirectResponse + public function publishDateEditView(Episode $episode): string|RedirectResponse { // only accessible if episode is already published - if ($this->episode->publication_status !== 'published') { - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + if ($episode->publication_status !== 'published') { + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with( 'error', - lang('Episode.publish_date_edit_error') + lang('Episode.publish_date_edit_error'), ); } helper('form'); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, ]; + $this->setHtmlHead(lang('Episode.publish_date_edit')); replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->episode->title, + 0 => $episode->podcast->title, + 1 => $episode->title, ]); - return view('episode/publish_date_edit', $data); } @@ -714,7 +679,7 @@ class EpisodeController extends BaseController * Prevents setting a future date as it does not make sense to set a future published date to an already published * episode. This also prevents any side-effects from occurring. */ - public function attemptPublishDateEdit(): RedirectResponse + public function publishDateEditAction(Episode $episode): RedirectResponse { $rules = [ 'new_publication_date' => 'valid_date[Y-m-d H:i]', @@ -727,7 +692,9 @@ class EpisodeController extends BaseController ->with('errors', $this->validator->getErrors()); } - $newPublicationDate = $this->request->getPost('new_publication_date'); + $validData = $this->validator->getValidated(); + + $newPublicationDate = $validData['new_publication_date']; $newPublicationDate = Time::createFromFormat( 'Y-m-d H:i', @@ -742,46 +709,47 @@ class EpisodeController extends BaseController ->with('error', lang('Episode.publish_date_edit_future_error')); } - $this->episode->published_at = $newPublicationDate; + $episode->published_at = $newPublicationDate; $episodeModel = new EpisodeModel(); - if (! $episodeModel->update($this->episode->id, $this->episode)) { + if (! $episodeModel->update($episode->id, $episode)) { return redirect() ->back() ->withInput() ->with('errors', $episodeModel->errors()); } - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with( 'message', - lang('Episode.publish_date_edit_success') + lang('Episode.publish_date_edit_success'), ); } - public function unpublish(): string | RedirectResponse + public function unpublishView(Episode $episode): string | RedirectResponse { - if ($this->episode->publication_status !== 'published') { - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with( + if ($episode->publication_status !== 'published') { + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with( 'error', - lang('Episode.unpublish_error') + lang('Episode.unpublish_error'), ); } helper(['form']); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, ]; + $this->setHtmlHead(lang('Episode.unpublish')); replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->episode->title, + 0 => $episode->podcast->title, + 1 => $episode->title, ]); return view('episode/unpublish', $data); } - public function attemptUnpublish(): RedirectResponse + public function unpublishAction(Episode $episode): RedirectResponse { $rules = [ 'understand' => 'required', @@ -798,32 +766,34 @@ class EpisodeController extends BaseController $db->transStart(); - $allPostsLinkedToEpisode = (new PostModel()) + $allPostsLinkedToEpisode = new PostModel() ->where([ - 'episode_id' => $this->episode->id, + 'episode_id' => $episode->id, 'in_reply_to_id' => null, - 'reblog_of_id' => null, + 'reblog_of_id' => null, ]) ->findAll(); foreach ($allPostsLinkedToEpisode as $post) { - (new PostModel())->removePost($post); + new PostModel() + ->removePost($post); } - $allCommentsLinkedToEpisode = (new EpisodeCommentModel()) + $allCommentsLinkedToEpisode = new EpisodeCommentModel() ->where([ - 'episode_id' => $this->episode->id, + 'episode_id' => $episode->id, 'in_reply_to_id' => null, ]) ->findAll(); foreach ($allCommentsLinkedToEpisode as $comment) { - (new EpisodeCommentModel())->removeComment($comment); + new EpisodeCommentModel() + ->removeComment($comment); } // set episode published_at to null to unpublish - $this->episode->published_at = null; + $episode->published_at = null; $episodeModel = new EpisodeModel(); - if (! $episodeModel->update($this->episode->id, $this->episode)) { + if (! $episodeModel->update($episode->id, $episode)) { $db->transRollback(); return redirect() ->back() @@ -832,32 +802,34 @@ class EpisodeController extends BaseController } // set podcast is_published_on_hubs to false to trigger websub push - (new PodcastModel())->update($this->episode->podcast->id, [ - 'is_published_on_hubs' => false, - ]); + new PodcastModel() + ->update($episode->podcast_id, [ + 'is_published_on_hubs' => 0, + ]); $db->transComplete(); - return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]); + return redirect()->route('episode-view', [$episode->podcast_id, $episode->id]); } - public function delete(): string + public function deleteView(Episode $episode): string { helper(['form']); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, ]; + $this->setHtmlHead(lang('Episode.delete')); replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/delete', $data); } - public function attemptDelete(): RedirectResponse + public function deleteAction(Episode $episode): RedirectResponse { $rules = [ 'understand' => 'required', @@ -870,7 +842,7 @@ class EpisodeController extends BaseController ->with('errors', $this->validator->getErrors()); } - if ($this->episode->published_at !== null) { + if ($episode->published_at instanceof Time) { return redirect() ->back() ->withInput() @@ -883,7 +855,7 @@ class EpisodeController extends BaseController $episodeModel = new EpisodeModel(); - if (! $episodeModel->delete($this->episode->id)) { + if (! $episodeModel->delete($episode->id)) { $db->transRollback(); return redirect() ->back() @@ -891,16 +863,18 @@ class EpisodeController extends BaseController ->with('errors', $episodeModel->errors()); } - $episodeMediaList = [$this->episode->transcript, $this->episode->chapters, $this->episode->audio]; + $episodeMediaList = [$episode->transcript, $episode->chapters, $episode->audio]; //only delete episode cover if different from podcast's - if ($this->episode->cover_id !== null) { - $episodeMediaList[] = $this->episode->cover; + if ($episode->cover_id !== null) { + $episodeMediaList[] = $episode->cover; } + $mediaModel = new MediaModel(); + //delete episode media records from database foreach ($episodeMediaList as $episodeMedia) { - if ($episodeMedia !== null && ! $episodeMedia->delete()) { + if ($episodeMedia !== null && ! $mediaModel->delete($episodeMedia->id)) { $db->transRollback(); return redirect() ->back() @@ -917,45 +891,52 @@ class EpisodeController extends BaseController //remove episode media files from disk foreach ($episodeMediaList as $episodeMedia) { - if ($episodeMedia !== null && ! $episodeMedia->deleteFile()) { - $warnings[] = lang('Episode.messages.deleteFileError', [ - 'type' => $episodeMedia->type, - 'file_path' => $episodeMedia->file_path, - ]); + if ($episodeMedia === null) { + continue; } + + if ($episodeMedia->deleteFile()) { + continue; + } + + $warnings[] = lang('Episode.messages.deleteFileError', [ + 'type' => $episodeMedia->type, + 'file_key' => $episodeMedia->file_key, + ]); } if ($warnings !== []) { return redirect() - ->route('episode-list', [$this->podcast->id]) + ->route('episode-list', [$episode->podcast_id]) ->with('message', lang('Episode.messages.deleteSuccess')) ->with('warnings', $warnings); } - return redirect()->route('episode-list', [$this->podcast->id])->with( + return redirect()->route('episode-list', [$episode->podcast_id])->with( 'message', - lang('Episode.messages.deleteSuccess') + lang('Episode.messages.deleteSuccess'), ); } - public function embed(): string + public function embedView(Episode $episode): string { helper(['form']); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, - 'themes' => EpisodeModel::$themes, + 'podcast' => $episode->podcast, + 'episode' => $episode, + 'themes' => EpisodeModel::$themes, ]; + $this->setHtmlHead(lang('Episode.embed.title')); replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/embed', $data); } - public function attemptCommentCreate(): RedirectResponse + public function commentCreateAction(Episode $episode): RedirectResponse { $rules = [ 'message' => 'required|max_length[500]', @@ -968,12 +949,12 @@ class EpisodeController extends BaseController ->with('errors', $this->validator->getErrors()); } - $message = $this->request->getPost('message'); + $validData = $this->validator->getValidated(); $newComment = new EpisodeComment([ - 'actor_id' => interact_as_actor_id(), - 'episode_id' => $this->episode->id, - 'message' => $message, + 'actor_id' => interact_as_actor_id(), + 'episode_id' => $episode->id, + 'message' => $validData['message'], 'created_at' => new Time('now'), 'created_by' => user_id(), ]); @@ -992,7 +973,7 @@ class EpisodeController extends BaseController return redirect()->back(); } - public function attemptCommentReply(string $commentId): RedirectResponse + public function commentReplyAction(Episode $episode, string $commentId): RedirectResponse { $rules = [ 'message' => 'required|max_length[500]', @@ -1005,15 +986,15 @@ class EpisodeController extends BaseController ->with('errors', $this->validator->getErrors()); } - $message = $this->request->getPost('message'); + $validData = $this->validator->getValidated(); $newReply = new EpisodeComment([ - 'actor_id' => interact_as_actor_id(), - 'episode_id' => $this->episode->id, - 'message' => $message, + 'actor_id' => interact_as_actor_id(), + 'episode_id' => $episode->id, + 'message' => $validData['message'], 'in_reply_to_id' => $commentId, - 'created_at' => new Time('now'), - 'created_by' => user_id(), + 'created_at' => new Time('now'), + 'created_by' => user_id(), ]); $commentModel = new EpisodeCommentModel(); diff --git a/modules/Admin/Controllers/EpisodePersonController.php b/modules/Admin/Controllers/EpisodePersonController.php index ee46c04d..dd8c8e4a 100644 --- a/modules/Admin/Controllers/EpisodePersonController.php +++ b/modules/Admin/Controllers/EpisodePersonController.php @@ -20,52 +20,54 @@ use CodeIgniter\HTTP\RedirectResponse; class EpisodePersonController extends BaseController { - protected Podcast $podcast; - - protected Episode $episode; - public function _remap(string $method, string ...$params): mixed { - if (count($params) < 2) { + if ($params === []) { throw PageNotFoundException::forPageNotFound(); } - if ( - ($this->podcast = (new PodcastModel())->getPodcastById((int) $params[0])) && - ($this->episode = (new EpisodeModel()) - ->where([ - 'id' => $params[1], - 'podcast_id' => $params[0], - ]) - ->first()) - ) { - unset($params[1]); - unset($params[0]); + if (count($params) === 1) { + if (! ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast) { + throw PageNotFoundException::forPageNotFound(); + } - return $this->{$method}(...$params); + return $this->{$method}($podcast); } - throw PageNotFoundException::forPageNotFound(); + if ( + ! ($episode = new EpisodeModel()->getEpisodeById((int) $params[1])) instanceof Episode + ) { + throw PageNotFoundException::forPageNotFound(); + } + + unset($params[0]); + unset($params[1]); + + return $this->{$method}($episode, ...$params); } - public function index(): string + public function index(Episode $episode): string { helper('form'); $data = [ - 'episode' => $this->episode, - 'podcast' => $this->podcast, - 'personOptions' => (new PersonModel())->getPersonOptions(), - 'taxonomyOptions' => (new PersonModel())->getTaxonomyOptions(), + 'episode' => $episode, + 'podcast' => $episode->podcast, + 'personOptions' => new PersonModel() + ->getPersonOptions(), + 'taxonomyOptions' => new PersonModel() + ->getTaxonomyOptions(), ]; + + $this->setHtmlHead(lang('Person.episode_form.title')); replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/persons', $data); } - public function attemptAdd(): RedirectResponse + public function createAction(Episode $episode): RedirectResponse { $rules = [ 'persons' => 'required', @@ -78,19 +80,23 @@ class EpisodePersonController extends BaseController ->with('errors', $this->validator->getErrors()); } - (new PersonModel())->addEpisodePersons( - $this->podcast->id, - $this->episode->id, - $this->request->getPost('persons'), - $this->request->getPost('roles') ?? [], - ); + $validData = $this->validator->getValidated(); + + new PersonModel() + ->addEpisodePersons( + $episode->podcast_id, + $episode->id, + $validData['persons'], + $this->request->getPost('roles') ?? [], + ); return redirect()->back(); } - public function remove(string $personId): RedirectResponse + public function deleteAction(Episode $episode, string $personId): RedirectResponse { - (new PersonModel())->removePersonFromEpisode($this->podcast->id, $this->episode->id, (int) $personId); + new PersonModel() + ->removePersonFromEpisode($episode->podcast_id, $episode->id, (int) $personId); return redirect()->back(); } diff --git a/modules/Admin/Controllers/FediverseController.php b/modules/Admin/Controllers/FediverseController.php index d7cd40f5..55b9ffcd 100644 --- a/modules/Admin/Controllers/FediverseController.php +++ b/modules/Admin/Controllers/FediverseController.php @@ -10,32 +10,36 @@ declare(strict_types=1); namespace Modules\Admin\Controllers; +use CodeIgniter\HTTP\RedirectResponse; + class FediverseController extends BaseController { - public function dashboard(): string + public function dashboard(): RedirectResponse { - return view('fediverse/dashboard'); + return redirect()->route('fediverse-blocked-actors'); } - public function blockedActors(): string + public function blockedActorsView(): string { helper(['form']); $blockedActors = model('ActorModel', false) ->getBlockedActors(); + $this->setHtmlHead(lang('Fediverse.blocked_actors')); return view('fediverse/blocked_actors', [ 'blockedActors' => $blockedActors, ]); } - public function blockedDomains(): string + public function blockedDomainsView(): string { helper(['form']); $blockedDomains = model('BlockedDomainModel', false) ->getBlockedDomains(); + $this->setHtmlHead(lang('Fediverse.blocked_domains')); return view('fediverse/blocked_domains', [ 'blockedDomains' => $blockedDomains, ]); diff --git a/modules/Admin/Controllers/NotificationController.php b/modules/Admin/Controllers/NotificationController.php index f4bc5558..06f0cea1 100644 --- a/modules/Admin/Controllers/NotificationController.php +++ b/modules/Admin/Controllers/NotificationController.php @@ -27,81 +27,79 @@ class NotificationController extends BaseController public function _remap(string $method, string ...$params): mixed { + if ($params === []) { + throw PageNotFoundException::forPageNotFound(); + } + if ( - ($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) === null + ! ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast ) { throw PageNotFoundException::forPageNotFound(); } - $this->podcast = $podcast; + $params[0] = $podcast; if (count($params) > 1) { if ( - ! ($notification = (new NotificationModel()) - ->where([ - 'id' => $params[1], - ]) - ->first()) + ! ($notification = new NotificationModel()->find($params[1])) instanceof Notification ) { throw PageNotFoundException::forPageNotFound(); } - $this->notification = $notification; - - unset($params[1]); - unset($params[0]); + $params[1] = $notification; } return $this->{$method}(...$params); } - public function list(): string + public function list(Podcast $podcast): string { - $notifications = (new NotificationModel())->where('target_actor_id', $this->podcast->actor_id) + $notifications = new NotificationModel() + ->where('target_actor_id', $podcast->actor_id) ->orderBy('created_at', 'desc'); $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, 'notifications' => $notifications->paginate(10), - 'pager' => $notifications->pager, + 'pager' => $notifications->pager, ]; + $this->setHtmlHead(lang('Notifications.title')); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $podcast->at_handle, ]); - return view('podcast/notifications', $data); } - public function markAsRead(): RedirectResponse + public function markAllAsReadAction(Podcast $podcast): RedirectResponse { - $this->notification->read_at = new Time('now'); - $notificationModel = new NotificationModel(); - $notificationModel->update($this->notification->id, $this->notification); - - if ($this->notification->post_id === null) { - return redirect()->route('podcast-activity', [esc($this->podcast->handle)]); - } - - $post = (new PostModel())->getPostById($this->notification->post_id); - - return redirect()->route( - 'post', - [esc((new PodcastModel())->getPodcastByActorId($this->notification->actor_id)->handle), $post->id] - ); - } - - public function markAllAsRead(): RedirectResponse - { - $notifications = (new NotificationModel())->where('target_actor_id', $this->podcast->actor_id) - ->where('read_at', null) + $notifications = new NotificationModel() + ->where('target_actor_id', $podcast->actor_id) + ->where('read_at') ->findAll(); foreach ($notifications as $notification) { $notification->read_at = new Time('now'); - (new NotificationModel())->update($notification->id, $notification); + new NotificationModel() + ->update($notification->id, $notification); } return redirect()->back(); } + + public function markAsReadAction(Podcast $podcast, Notification $notification): RedirectResponse + { + $notification->read_at = new Time('now'); + $notificationModel = new NotificationModel(); + $notificationModel->update($notification->id, $notification); + + if ($notification->post_id === null) { + return redirect()->route('podcast-activity', [esc($podcast->handle)]); + } + + $post = new PostModel() + ->getPostById($notification->post_id); + + return redirect()->route('post', [$podcast->handle, $post->id]); + } } diff --git a/modules/Admin/Controllers/PageController.php b/modules/Admin/Controllers/PageController.php index 464f13f7..8c406463 100644 --- a/modules/Admin/Controllers/PageController.php +++ b/modules/Admin/Controllers/PageController.php @@ -17,16 +17,14 @@ use CodeIgniter\HTTP\RedirectResponse; class PageController extends BaseController { - protected ?Page $page; - public function _remap(string $method, string ...$params): mixed { if ($params === []) { return $this->{$method}(); } - if ($this->page = (new PageModel())->find($params[0])) { - return $this->{$method}(); + if (($page = new PageModel()->find($params[0])) instanceof Page) { + return $this->{$method}($page); } throw PageNotFoundException::forPageNotFound(); @@ -34,32 +32,36 @@ class PageController extends BaseController public function list(): string { + $this->setHtmlHead(lang('Page.all_pages')); $data = [ - 'pages' => (new PageModel())->findAll(), + 'pages' => new PageModel() + ->findAll(), ]; return view('page/list', $data); } - public function view(): string + public function view(Page $page): string { + $this->setHtmlHead($page->title); return view('page/view', [ - 'page' => $this->page, + 'page' => $page, ]); } - public function create(): string + public function createView(): string { helper('form'); + $this->setHtmlHead(lang('Page.create')); return view('page/create'); } - public function attemptCreate(): RedirectResponse + public function createAction(): RedirectResponse { $page = new Page([ - 'title' => $this->request->getPost('title'), - 'slug' => $this->request->getPost('slug'), + 'title' => $this->request->getPost('title'), + 'slug' => $this->request->getPost('slug'), 'content_markdown' => $this->request->getPost('content'), ]); @@ -79,39 +81,41 @@ class PageController extends BaseController ])); } - public function edit(): string + public function editView(Page $page): string { helper('form'); + $this->setHtmlHead(lang('Page.edit')); replace_breadcrumb_params([ - 0 => $this->page->title, + 0 => $page->title, ]); return view('page/edit', [ - 'page' => $this->page, + 'page' => $page, ]); } - public function attemptEdit(): RedirectResponse + public function editAction(Page $page): RedirectResponse { - $this->page->title = $this->request->getPost('title'); - $this->page->slug = $this->request->getPost('slug'); - $this->page->content_markdown = $this->request->getPost('content'); + $page->title = $this->request->getPost('title'); + $page->slug = $this->request->getPost('slug'); + $page->content_markdown = $this->request->getPost('content'); $pageModel = new PageModel(); - if (! $pageModel->update($this->page->id, $this->page)) { + if (! $pageModel->update($page->id, $page)) { return redirect() ->back() ->withInput() ->with('errors', $pageModel->errors()); } - return redirect()->route('page-edit', [$this->page->id])->with('message', lang('Page.messages.editSuccess')); + return redirect()->route('page-edit', [$page->id])->with('message', lang('Page.messages.editSuccess')); } - public function delete(): RedirectResponse + public function deleteAction(Page $page): RedirectResponse { - (new PageModel())->delete($this->page->id); + new PageModel() + ->delete($page->id); return redirect()->route('page-list'); } diff --git a/modules/Admin/Controllers/PersonController.php b/modules/Admin/Controllers/PersonController.php index 9c0ffcfe..95bc4a28 100644 --- a/modules/Admin/Controllers/PersonController.php +++ b/modules/Admin/Controllers/PersonController.php @@ -14,11 +14,10 @@ use App\Entities\Person; use App\Models\PersonModel; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; +use Modules\Media\Models\MediaModel; class PersonController extends BaseController { - protected ?Person $person; - public function _remap(string $method, string ...$params): mixed { if ($params === []) { @@ -26,47 +25,51 @@ class PersonController extends BaseController } if ( - ($this->person = (new PersonModel())->getPersonById((int) $params[0])) !== null + ($person = new PersonModel()->getPersonById((int) $params[0])) instanceof Person ) { - return $this->{$method}(); + return $this->{$method}($person); } throw PageNotFoundException::forPageNotFound(); } - public function index(): string + public function list(): string { $data = [ - 'persons' => (new PersonModel())->findAll(), + 'persons' => new PersonModel() + ->orderBy('full_name') + ->findAll(), ]; + $this->setHtmlHead(lang('Person.all_persons')); return view('person/list', $data); } - public function view(): string + public function view(Person $person): string { $data = [ - 'person' => $this->person, + 'person' => $person, ]; + $this->setHtmlHead($person->full_name); replace_breadcrumb_params([ - 0 => $this->person->full_name, + 0 => $person->full_name, ]); return view('person/view', $data); } - public function create(): string + public function createView(): string { helper(['form']); + $this->setHtmlHead(lang('Person.create')); return view('person/create'); } - public function attemptCreate(): RedirectResponse + public function createAction(): RedirectResponse { $rules = [ - 'avatar' => - 'is_image[avatar]|ext_in[avatar,jpg,jpeg,png]|min_dims[avatar,400,400]|is_image_ratio[avatar,1,1]', + 'avatar' => 'is_image[avatar]|ext_in[avatar,jpg,jpeg,png]|min_dims[avatar,400,400]|is_image_ratio[avatar,1,1]', ]; if (! $this->validate($rules)) { @@ -80,12 +83,12 @@ class PersonController extends BaseController $db->transStart(); $person = new Person([ - 'full_name' => $this->request->getPost('full_name'), - 'unique_name' => $this->request->getPost('unique_name'), + 'created_by' => user_id(), + 'updated_by' => user_id(), + 'full_name' => $this->request->getPost('full_name'), + 'unique_name' => $this->request->getPost('unique_name'), 'information_url' => $this->request->getPost('information_url'), - 'avatar' => $this->request->getFile('avatar'), - 'created_by' => user_id(), - 'updated_by' => user_id(), + 'avatar' => $this->request->getFile('avatar'), ]); $personModel = new PersonModel(); @@ -103,25 +106,25 @@ class PersonController extends BaseController ->with('message', lang('Person.messages.createSuccess')); } - public function edit(): string + public function editView(Person $person): string { helper('form'); $data = [ - 'person' => $this->person, + 'person' => $person, ]; + $this->setHtmlHead(lang('Person.edit')); replace_breadcrumb_params([ - 0 => $this->person->full_name, + 0 => $person->full_name, ]); return view('person/edit', $data); } - public function attemptEdit(): RedirectResponse + public function editAction(Person $person): RedirectResponse { $rules = [ - 'avatar' => - 'is_image[avatar]|ext_in[avatar,jpg,jpeg,png]|min_dims[avatar,400,400]|is_image_ratio[avatar,1,1]', + 'avatar' => 'is_image[avatar]|ext_in[avatar,jpg,jpeg,png]|min_dims[avatar,400,400]|is_image_ratio[avatar,1,1]', ]; if (! $this->validate($rules)) { @@ -131,30 +134,36 @@ class PersonController extends BaseController ->with('errors', $this->validator->getErrors()); } - $this->person->full_name = $this->request->getPost('full_name'); - $this->person->unique_name = $this->request->getPost('unique_name'); - $this->person->information_url = $this->request->getPost('information_url'); - $this->person->setAvatar($this->request->getFile('avatar')); - - $this->person->updated_by = user_id(); + $person->updated_by = user_id(); + $person->full_name = $this->request->getPost('full_name'); + $person->unique_name = $this->request->getPost('unique_name'); + $person->information_url = $this->request->getPost('information_url'); + $person->setAvatar($this->request->getFile('avatar')); $personModel = new PersonModel(); - if (! $personModel->update($this->person->id, $this->person)) { + if (! $personModel->update($person->id, $person)) { return redirect() ->back() ->withInput() ->with('errors', $personModel->errors()); } - return redirect()->route('person-edit', [$this->person->id])->with( + return redirect()->route('person-edit', [$person->id])->with( 'message', - lang('Person.messages.editSuccess') + lang('Person.messages.editSuccess'), ); } - public function delete(): RedirectResponse + public function deleteAction(Person $person): RedirectResponse { - (new PersonModel())->delete($this->person->id); + if ($person->avatar_id !== null) { + // delete avatar to prevent collision if recreating person + new MediaModel() + ->deleteMedia($person->avatar); + } + + new PersonModel() + ->delete($person->id); return redirect()->route('person-list') ->with('message', lang('Person.messages.deleteSuccess')); diff --git a/modules/Admin/Controllers/PodcastController.php b/modules/Admin/Controllers/PodcastController.php index 7e9726af..96204b66 100644 --- a/modules/Admin/Controllers/PodcastController.php +++ b/modules/Admin/Controllers/PodcastController.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace Modules\Admin\Controllers; +use App\Entities\Actor; use App\Entities\Location; use App\Entities\Podcast; use App\Entities\Post; @@ -17,13 +18,11 @@ use App\Models\ActorModel; use App\Models\CategoryModel; use App\Models\EpisodeModel; use App\Models\LanguageModel; -use App\Models\MediaModel; use App\Models\PodcastModel; use App\Models\PostModel; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\I18n\Time; -use Config\Services; use Modules\Analytics\Models\AnalyticsPodcastByCountryModel; use Modules\Analytics\Models\AnalyticsPodcastByEpisodeModel; use Modules\Analytics\Models\AnalyticsPodcastByHourModel; @@ -33,11 +32,12 @@ use Modules\Analytics\Models\AnalyticsPodcastModel; use Modules\Analytics\Models\AnalyticsWebsiteByBrowserModel; use Modules\Analytics\Models\AnalyticsWebsiteByEntryPageModel; use Modules\Analytics\Models\AnalyticsWebsiteByRefererModel; +use Modules\Media\Entities\Image; +use Modules\Media\FileManagers\FileManagerInterface; +use Modules\Media\Models\MediaModel; class PodcastController extends BaseController { - protected Podcast $podcast; - public function _remap(string $method, string ...$params): mixed { if ($params === []) { @@ -45,10 +45,9 @@ class PodcastController extends BaseController } if ( - ($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) !== null + ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast ) { - $this->podcast = $podcast; - return $this->{$method}(); + return $this->{$method}($podcast); } throw PageNotFoundException::forPageNotFound(); @@ -56,137 +55,149 @@ class PodcastController extends BaseController public function list(): string { - if (! has_permission('podcasts-list')) { + if (auth()->user()->can('podcasts.view')) { $data = [ - 'podcasts' => (new PodcastModel())->getUserPodcasts((int) user_id()), + 'podcasts' => new PodcastModel() + ->findAll(), ]; } else { $data = [ - 'podcasts' => (new PodcastModel())->findAll(), + 'podcasts' => get_user_podcasts(auth()->user()), ]; } + $this->setHtmlHead(lang('Podcast.all_podcasts')); return view('podcast/list', $data); } - public function view(): string + public function view(Podcast $podcast): string { $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead($podcast->title); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $podcast->at_handle, ]); return view('podcast/view', $data); } - public function viewAnalytics(): string + public function analyticsView(Podcast $podcast): string { $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead($podcast->title); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $podcast->at_handle, ]); return view('podcast/analytics/index', $data); } - public function viewAnalyticsWebpages(): string + public function analyticsWebpagesView(Podcast $podcast): string { $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead($podcast->title); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $podcast->at_handle, ]); return view('podcast/analytics/webpages', $data); } - public function viewAnalyticsLocations(): string + public function analyticsLocationsView(Podcast $podcast): string { $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead($podcast->title); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $podcast->at_handle, ]); return view('podcast/analytics/locations', $data); } - public function viewAnalyticsUniqueListeners(): string + public function analyticsUniqueListenersView(Podcast $podcast): string { $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead($podcast->title); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $podcast->at_handle, ]); return view('podcast/analytics/unique_listeners', $data); } - public function viewAnalyticsListeningTime(): string + public function analyticsListeningTimeView(Podcast $podcast): string { $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead($podcast->title); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $podcast->at_handle, ]); return view('podcast/analytics/listening_time', $data); } - public function viewAnalyticsTimePeriods(): string + public function analyticsTimePeriodsView(Podcast $podcast): string { $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead($podcast->title); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $podcast->at_handle, ]); return view('podcast/analytics/time_periods', $data); } - public function viewAnalyticsPlayers(): string + public function analyticsPlayersView(Podcast $podcast): string { $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead($podcast->title); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $podcast->at_handle, ]); return view('podcast/analytics/players', $data); } - public function create(): string + public function createView(): string { helper(['form', 'misc']); - $languageOptions = (new LanguageModel())->getLanguageOptions(); - $categoryOptions = (new CategoryModel())->getCategoryOptions(); + $languageOptions = new LanguageModel() + ->getLanguageOptions(); + $categoryOptions = new CategoryModel() + ->getCategoryOptions(); $data = [ 'languageOptions' => $languageOptions, 'categoryOptions' => $categoryOptions, - 'browserLang' => get_browser_language($this->request->getServer('HTTP_ACCEPT_LANGUAGE')), + 'browserLang' => get_browser_language($this->request->getServer('HTTP_ACCEPT_LANGUAGE')), ]; + $this->setHtmlHead(lang('Podcast.create')); return view('podcast/create', $data); } - public function attemptCreate(): RedirectResponse + public function createAction(): RedirectResponse { $rules = [ - 'cover' => - 'uploaded[cover]|is_image[cover]|ext_in[cover,jpg,png]|min_dims[cover,1400,1400]|is_image_ratio[cover,1,1]', - 'banner' => 'is_image[banner]|ext_in[banner,jpg,png]|min_dims[banner,1500,500]|is_image_ratio[banner,3,1]', + 'cover' => 'uploaded[cover]|is_image[cover]|ext_in[cover,jpg,jpeg,png]|min_dims[cover,1400,1400]|is_image_ratio[cover,1,1]', + 'banner' => 'is_image[banner]|ext_in[banner,jpg,jpeg,png]|min_dims[banner,1500,500]|is_image_ratio[banner,3,1]', ]; if (! $this->validate($rules)) { @@ -196,52 +207,35 @@ class PodcastController extends BaseController ->with('errors', $this->validator->getErrors()); } - if ( - ($partnerId = $this->request->getPost('partner_id')) === '' || - ($partnerLinkUrl = $this->request->getPost('partner_link_url')) === '' || - ($partnerImageUrl = $this->request->getPost('partner_image_url')) === '') { - $partnerId = null; - $partnerLinkUrl = null; - $partnerImageUrl = null; - } - $db = db_connect(); $db->transStart(); $newPodcast = new Podcast([ - 'title' => $this->request->getPost('title'), - 'handle' => $this->request->getPost('handle'), - 'cover' => $this->request->getFile('cover'), - 'banner' => $this->request->getFile('banner'), + 'created_by' => user_id(), + 'updated_by' => user_id(), + 'title' => $this->request->getPost('title'), + 'handle' => $this->request->getPost('handle'), + 'cover' => $this->request->getFile('cover'), + 'banner' => $this->request->getFile('banner'), 'description_markdown' => $this->request->getPost('description'), - 'language_code' => $this->request->getPost('language'), - 'category_id' => $this->request->getPost('category'), - 'parental_advisory' => - $this->request->getPost('parental_advisory') !== 'undefined' + 'language_code' => $this->request->getPost('language'), + 'category_id' => $this->request->getPost('category'), + 'parental_advisory' => $this->request->getPost('parental_advisory') !== 'undefined' ? $this->request->getPost('parental_advisory') : null, - 'owner_name' => $this->request->getPost('owner_name'), + 'owner_name' => $this->request->getPost('owner_name'), 'owner_email' => $this->request->getPost('owner_email'), - 'publisher' => $this->request->getPost('publisher'), - 'type' => $this->request->getPost('type'), - 'copyright' => $this->request->getPost('copyright'), - 'location' => $this->request->getPost('location_name') === '' ? null : new Location($this->request->getPost( - 'location_name' - )), - 'payment_pointer' => $this->request->getPost( - 'payment_pointer' - ) === '' ? null : $this->request->getPost('payment_pointer'), - 'custom_rss_string' => $this->request->getPost('custom_rss'), - 'partner_id' => $partnerId, - 'partner_link_url' => $partnerLinkUrl, - 'partner_image_url' => $partnerImageUrl, - 'is_blocked' => $this->request->getPost('block') === 'yes', - 'is_completed' => $this->request->getPost('complete') === 'yes', - 'is_locked' => $this->request->getPost('lock') === 'yes', + 'publisher' => $this->request->getPost('publisher'), + 'type' => $this->request->getPost('type'), + 'copyright' => $this->request->getPost('copyright'), + 'location' => $this->request->getPost('location_name') === '' ? null : new Location( + $this->request->getPost('location_name'), + ), + 'is_blocked' => $this->request->getPost('block') === 'yes', + 'is_completed' => $this->request->getPost('complete') === 'yes', + 'is_locked' => $this->request->getPost('lock') === 'yes', 'is_premium_by_default' => $this->request->getPost('premium_by_default') === 'yes', - 'created_by' => user_id(), - 'updated_by' => user_id(), - 'published_at' => null, + 'published_at' => null, ]); $podcastModel = new PodcastModel(); @@ -253,54 +247,51 @@ class PodcastController extends BaseController ->with('errors', $podcastModel->errors()); } - $authorize = Services::authorization(); - $podcastAdminGroup = $authorize->group('podcast_admin'); - - $podcastModel->addPodcastContributor(user_id(), $newPodcastId, (int) $podcastAdminGroup->id); + // generate podcast roles and permissions + // before setting current user as podcast admin + config('AuthGroups') + ->generatePodcastAuthorizations($newPodcastId); + add_podcast_group(auth()->user(), (int) $newPodcastId, setting('AuthGroups.mostPowerfulPodcastGroup')); // set Podcast categories - (new CategoryModel())->setPodcastCategories( - (int) $newPodcastId, - $this->request->getPost('other_categories') ?? [], - ); - - // set interact as the newly created podcast actor - $createdPodcast = (new PodcastModel())->getPodcastById($newPodcastId); - set_interact_as_actor($createdPodcast->actor_id); + new CategoryModel() + ->setPodcastCategories((int) $newPodcastId, $this->request->getPost('other_categories') ?? []); $db->transComplete(); return redirect()->route('podcast-view', [$newPodcastId])->with( 'message', - lang('Podcast.messages.createSuccess') + lang('Podcast.messages.createSuccess'), ); } - public function edit(): string + public function editView(Podcast $podcast): string { helper('form'); - $languageOptions = (new LanguageModel())->getLanguageOptions(); - $categoryOptions = (new CategoryModel())->getCategoryOptions(); + $languageOptions = new LanguageModel() + ->getLanguageOptions(); + $categoryOptions = new CategoryModel() + ->getCategoryOptions(); $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, 'languageOptions' => $languageOptions, 'categoryOptions' => $categoryOptions, ]; + $this->setHtmlHead(lang('Podcast.edit')); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $podcast->at_handle, ]); return view('podcast/edit', $data); } - public function attemptEdit(): RedirectResponse + public function editAction(Podcast $podcast): RedirectResponse { $rules = [ - 'cover' => - 'is_image[cover]|ext_in[cover,jpg,png]|min_dims[cover,1400,1400]|is_image_ratio[cover,1,1]', - 'banner' => 'is_image[banner]|ext_in[banner,jpg,png]|min_dims[banner,1500,500]|is_image_ratio[banner,3,1]', + 'cover' => 'is_image[cover]|ext_in[cover,jpg,jpeg,png]|min_dims[cover,1400,1400]|is_image_ratio[cover,1,1]', + 'banner' => 'is_image[banner]|ext_in[banner,jpg,jpeg,png]|min_dims[banner,1500,500]|is_image_ratio[banner,3,1]', ]; if (! $this->validate($rules)) { @@ -310,60 +301,46 @@ class PodcastController extends BaseController ->with('errors', $this->validator->getErrors()); } - if ( - ($partnerId = $this->request->getPost('partner_id')) === '' || - ($partnerLinkUrl = $this->request->getPost('partner_link_url')) === '' || - ($partnerImageUrl = $this->request->getPost('partner_image_url')) === '') { - $partnerId = null; - $partnerLinkUrl = null; - $partnerImageUrl = null; - } + $podcast->updated_by = (int) user_id(); - $this->podcast->title = $this->request->getPost('title'); - $this->podcast->description_markdown = $this->request->getPost('description'); - $this->podcast->setCover($this->request->getFile('cover')); - $this->podcast->setBanner($this->request->getFile('banner')); + $podcast->title = $this->request->getPost('title'); + $podcast->description_markdown = $this->request->getPost('description'); + $podcast->setCover($this->request->getFile('cover')); + $podcast->setBanner($this->request->getFile('banner')); - $this->podcast->language_code = $this->request->getPost('language'); - $this->podcast->category_id = $this->request->getPost('category'); - $this->podcast->parental_advisory = + $podcast->language_code = $this->request->getPost('language'); + $podcast->category_id = $this->request->getPost('category'); + $podcast->parental_advisory = $this->request->getPost('parental_advisory') !== 'undefined' ? $this->request->getPost('parental_advisory') : null; - $this->podcast->publisher = $this->request->getPost('publisher'); - $this->podcast->owner_name = $this->request->getPost('owner_name'); - $this->podcast->owner_email = $this->request->getPost('owner_email'); - $this->podcast->type = $this->request->getPost('type'); - $this->podcast->copyright = $this->request->getPost('copyright'); - $this->podcast->location = $this->request->getPost('location_name') === '' ? null : new Location( - $this->request->getPost('location_name') + $podcast->publisher = $this->request->getPost('publisher'); + $podcast->owner_name = $this->request->getPost('owner_name'); + $podcast->owner_email = $this->request->getPost('owner_email'); + $podcast->type = $this->request->getPost('type'); + $podcast->copyright = $this->request->getPost('copyright'); + $podcast->location = $this->request->getPost('location_name') === '' ? null : new Location( + $this->request->getPost('location_name'), ); - $this->podcast->payment_pointer = $this->request->getPost( - 'payment_pointer' - ) === '' ? null : $this->request->getPost('payment_pointer'); - $this->podcast->custom_rss_string = $this->request->getPost('custom_rss'); - $this->podcast->new_feed_url = $this->request->getPost('new_feed_url') === '' ? null : $this->request->getPost( - 'new_feed_url' + $podcast->new_feed_url = $this->request->getPost('new_feed_url') === '' ? null : $this->request->getPost( + 'new_feed_url', ); - $this->podcast->partner_id = $partnerId; - $this->podcast->partner_link_url = $partnerLinkUrl; - $this->podcast->partner_image_url = $partnerImageUrl; - $this->podcast->is_blocked = $this->request->getPost('block') === 'yes'; - $this->podcast->is_completed = + + $podcast->is_blocked = $this->request->getPost('block') === 'yes'; + $podcast->is_completed = $this->request->getPost('complete') === 'yes'; - $this->podcast->is_locked = $this->request->getPost('lock') === 'yes'; - $this->podcast->is_premium_by_default = $this->request->getPost('premium_by_default') === 'yes'; - $this->podcast->updated_by = (int) user_id(); + $podcast->is_locked = $this->request->getPost('lock') === 'yes'; + $podcast->is_premium_by_default = $this->request->getPost('premium_by_default') === 'yes'; // republish on websub hubs upon edit - $this->podcast->is_published_on_hubs = false; + $podcast->is_published_on_hubs = false; $db = db_connect(); $db->transStart(); $podcastModel = new PodcastModel(); - if (! $podcastModel->update($this->podcast->id, $this->podcast)) { + if (! $podcastModel->update($podcast->id, $podcast)) { $db->transRollback(); return redirect() ->back() @@ -372,22 +349,28 @@ class PodcastController extends BaseController } // set Podcast categories - (new CategoryModel())->setPodcastCategories( - $this->podcast->id, - $this->request->getPost('other_categories') ?? [], - ); + new CategoryModel() + ->setPodcastCategories($podcast->id, $this->request->getPost('other_categories') ?? []); + + // New feed url redirect + service('settings') + ->set( + 'Podcast.redirect_to_new_feed', + $this->request->getPost('redirect_to_new_feed') === 'yes', + 'podcast:' . $podcast->id, + ); $db->transComplete(); - return redirect()->route('podcast-edit', [$this->podcast->id])->with( + return redirect()->route('podcast-edit', [$podcast->id])->with( 'message', - lang('Podcast.messages.editSuccess') + lang('Podcast.messages.editSuccess'), ); } - public function deleteBanner(): RedirectResponse + public function deleteBannerAction(Podcast $podcast): RedirectResponse { - if ($this->podcast->banner === null) { + if (! $podcast->banner instanceof Image) { return redirect()->back(); } @@ -396,25 +379,28 @@ class PodcastController extends BaseController $db->transStart(); $mediaModel = new MediaModel(); - if (! $mediaModel->deleteMedia($this->podcast->banner)) { + if (! $mediaModel->deleteMedia($podcast->banner)) { return redirect() ->back() ->withInput() ->with('errors', $mediaModel->errors()); } - (new PodcastModel())->clearCache([ - 'id' => $this->podcast->id, - ]); + new PodcastModel() + ->clearCache([ + 'id' => $podcast->id, + ]); // remove banner url from actor - $actor = (new ActorModel())->getActorById($this->podcast->actor_id); + $actor = new ActorModel() + ->getActorById($podcast->actor_id); - if ($actor !== null) { + if ($actor instanceof Actor) { $actor->cover_image_url = null; $actor->cover_image_mimetype = null; - (new ActorModel())->update($actor->id, $actor); + new ActorModel() + ->update($actor->id, $actor); } $db->transComplete(); @@ -422,34 +408,37 @@ class PodcastController extends BaseController return redirect()->back(); } - public function latestEpisodes(int $limit, int $podcastId): string + public function latestEpisodesView(int $limit, int $podcastId): string { - $episodes = (new EpisodeModel()) + $episodes = new EpisodeModel() ->where('podcast_id', $podcastId) + ->orderBy('-`published_at`', '', false) ->orderBy('created_at', 'desc') ->findAll($limit); return view('podcast/latest_episodes', [ 'episodes' => $episodes, - 'podcast' => (new PodcastModel())->getPodcastById($podcastId), + 'podcast' => new PodcastModel() + ->getPodcastById($podcastId), ]); } - public function delete(): string + public function deleteView(Podcast $podcast): string { helper(['form']); $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead(lang('Podcast.delete')); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $podcast->at_handle, ]); return view('podcast/delete', $data); } - public function attemptDelete(): RedirectResponse + public function deleteAction(Podcast $podcast): RedirectResponse { $rules = [ 'understand' => 'required', @@ -467,7 +456,8 @@ class PodcastController extends BaseController $db->transStart(); //delete podcast episodes - $podcastEpisodes = (new EpisodeModel())->where('podcast_id', $this->podcast->id) + $podcastEpisodes = new EpisodeModel() + ->where('podcast_id', $podcast->id) ->findAll(); foreach ($podcastEpisodes as $podcastEpisode) { @@ -488,15 +478,17 @@ class PodcastController extends BaseController $episodeMediaList[] = $podcastEpisode->cover; } + $mediaModel = new MediaModel(); + foreach ($episodeMediaList as $episodeMedia) { - if ($episodeMedia !== null && ! $episodeMedia->delete()) { + if ($episodeMedia !== null && ! $mediaModel->delete($episodeMedia->id)) { $db->transRollback(); return redirect() ->back() ->withInput() ->with('error', lang('Podcast.messages.deleteEpisodeMediaError', [ 'episode_slug' => $podcastEpisode->slug, - 'type' => $episodeMedia->type, + 'type' => $episodeMedia->type, ])); } } @@ -505,7 +497,7 @@ class PodcastController extends BaseController //delete podcast $podcastModel = new PodcastModel(); - if (! $podcastModel->delete($this->podcast->id)) { + if (! $podcastModel->delete($podcast->id)) { $db->transRollback(); return redirect() ->back() @@ -517,20 +509,22 @@ class PodcastController extends BaseController $podcastMediaList = [ [ 'type' => 'cover', - 'file' => $this->podcast->cover, + 'file' => $podcast->cover, ], ]; - if ($this->podcast->banner_id !== null) { + if ($podcast->banner_id !== null) { $podcastMediaList[] = [ 'type' => 'banner', - 'file' => $this->podcast->banner, + 'file' => $podcast->banner, ]; } + $mediaModel = new MediaModel(); + foreach ($podcastMediaList as $podcastMedia) { - if ($podcastMedia['file'] !== null && ! $podcastMedia['file']->delete()) { + if ($podcastMedia['file'] instanceof Image && ! $mediaModel->delete($podcastMedia['file']->id)) { $db->transRollback(); return redirect() ->back() @@ -544,7 +538,7 @@ class PodcastController extends BaseController //delete podcast actor $actorModel = new ActorModel(); - if (! $actorModel->delete($this->podcast->actor_id)) { + if (! $actorModel->delete($podcast->actor_id)) { $db->transRollback(); return redirect() ->back() @@ -566,7 +560,7 @@ class PodcastController extends BaseController ]; foreach ($analyticsModels as $analyticsModel) { if (! $analyticsModel->where([ - 'podcast_id' => $this->podcast->id, + 'podcast_id' => $podcast->id, ])->delete()) { $db->transRollback(); return redirect() @@ -576,29 +570,17 @@ class PodcastController extends BaseController } } - if ($this->podcast->actor_id === interact_as_actor_id()) { - //set interact to the most recently created podcast actor - $mostRecentPodcast = (new PodcastModel())->orderBy('created_at', 'desc') - ->first(); - if ($mostRecentPodcast !== null) { - set_interact_as_actor($mostRecentPodcast->actor_id); - } - } - $db->transComplete(); + /** @var FileManagerInterface $fileManager */ + $fileManager = service('file_manager'); + //delete podcast media files and folder - $folder = 'podcasts/' . $this->podcast->handle; - - $mediaRoot = config('App') - ->mediaRoot . '/' . $folder; - - helper('filesystem'); - - if (! delete_files($mediaRoot) || ! rmdir($mediaRoot)) { + $folder = 'podcasts/' . $podcast->handle; + if (! $fileManager->deleteAll($folder)) { return redirect()->route('podcast-list') ->with('message', lang('Podcast.messages.deleteSuccess', [ - 'podcast_handle' => $this->podcast->handle, + 'podcast_handle' => $podcast->handle, ])) ->with('warning', lang('Podcast.messages.deletePodcastMediaFolderError', [ 'folder_path' => $folder, @@ -607,38 +589,37 @@ class PodcastController extends BaseController return redirect()->route('podcast-list') ->with('message', lang('Podcast.messages.deleteSuccess', [ - 'podcast_handle' => $this->podcast->handle, + 'podcast_handle' => $podcast->handle, ])); } - public function publish(): string | RedirectResponse + public function publishView(Podcast $podcast): string | RedirectResponse { helper(['form']); $data = [ - 'podcast' => $this->podcast, + 'podcast' => $podcast, ]; + $this->setHtmlHead(lang('Podcast.publish')); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $podcast->at_handle, ]); - return view('podcast/publish', $data); } - public function attemptPublish(): RedirectResponse + public function publishAction(Podcast $podcast): RedirectResponse { - if ($this->podcast->publication_status !== 'not_published') { - return redirect()->route('podcast-view', [$this->podcast->id])->with( + if ($podcast->publication_status !== 'not_published') { + return redirect()->route('podcast-view', [$podcast->id])->with( 'error', - lang('Podcast.messages.publishError') + lang('Podcast.messages.publishError'), ); } $rules = [ - 'publication_method' => 'required', - 'scheduled_publication_date' => - 'valid_date[Y-m-d H:i]|permit_empty', + 'publication_method' => 'required', + 'scheduled_publication_date' => 'valid_date[Y-m-d H:i]|permit_empty', ]; if (! $this->validate($rules)) { @@ -648,14 +629,16 @@ class PodcastController extends BaseController ->with('errors', $this->validator->getErrors()); } + $validData = $this->validator->getValidated(); + $db = db_connect(); $db->transStart(); - $publishMethod = $this->request->getPost('publication_method'); + $publishMethod = $validData['publication_method']; if ($publishMethod === 'schedule') { - $scheduledPublicationDate = $this->request->getPost('scheduled_publication_date'); + $scheduledPublicationDate = $validData['scheduled_publication_date']; if ($scheduledPublicationDate) { - $this->podcast->published_at = Time::createFromFormat( + $podcast->published_at = Time::createFromFormat( 'Y-m-d H:i', $scheduledPublicationDate, $this->request->getPost('client_timezone'), @@ -668,19 +651,19 @@ class PodcastController extends BaseController ->with('error', lang('Podcast.messages.scheduleDateError')); } } else { - $this->podcast->published_at = Time::now(); + $podcast->published_at = Time::now(); } $message = $this->request->getPost('message'); // only create post if message is not empty if ($message !== '') { $newPost = new Post([ - 'actor_id' => $this->podcast->actor_id, - 'message' => $message, + 'actor_id' => $podcast->actor_id, + 'message' => $message, 'created_by' => user_id(), ]); - $newPost->published_at = $this->podcast->published_at; + $newPost->published_at = $podcast->published_at; $postModel = new PostModel(); if (! $postModel->addPost($newPost)) { @@ -692,13 +675,13 @@ class PodcastController extends BaseController } } - $episodes = (new EpisodeModel()) - ->where('podcast_id', $this->podcast->id) - ->where('published_at !=', null) + $episodes = new EpisodeModel() + ->where('podcast_id', $podcast->id) + ->where('published_at !=') ->findAll(); foreach ($episodes as $episode) { - $episode->published_at = $this->podcast->published_at->addSeconds(1); + $episode->published_at = $podcast->published_at->addSeconds(1); $episodeModel = new EpisodeModel(); if (! $episodeModel->update($episode->id, $episode)) { @@ -709,10 +692,11 @@ class PodcastController extends BaseController ->with('errors', $episodeModel->errors()); } - $post = (new PostModel())->where('episode_id', $episode->id) + $post = new PostModel() + ->where('episode_id', $episode->id) ->first(); - if ($post !== null) { + if ($post instanceof Post) { $post->published_at = $episode->published_at; $postModel = new PostModel(); if (! $postModel->update($post->id, $post)) { @@ -726,7 +710,7 @@ class PodcastController extends BaseController } $podcastModel = new PodcastModel(); - if (! $podcastModel->update($this->podcast->id, $this->podcast)) { + if (! $podcastModel->update($podcast->id, $podcast)) { $db->transRollback(); return redirect() ->back() @@ -736,43 +720,42 @@ class PodcastController extends BaseController $db->transComplete(); - return redirect()->route('podcast-view', [$this->podcast->id]); + return redirect()->route('podcast-view', [$podcast->id]); } - public function publishEdit(): string | RedirectResponse + public function publishEditView(Podcast $podcast): string | RedirectResponse { helper(['form']); $data = [ - 'podcast' => $this->podcast, - 'post' => (new PostModel()) + 'podcast' => $podcast, + 'post' => new PostModel() ->where([ - 'actor_id' => $this->podcast->actor_id, + 'actor_id' => $podcast->actor_id, 'episode_id' => null, ]) ->first(), ]; + $this->setHtmlHead(lang('Podcast.publish_edit')); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $podcast->at_handle, ]); - return view('podcast/publish_edit', $data); } - public function attemptPublishEdit(): RedirectResponse + public function publishEditAction(Podcast $podcast): RedirectResponse { - if ($this->podcast->publication_status !== 'scheduled') { - return redirect()->route('podcast-view', [$this->podcast->id])->with( + if ($podcast->publication_status !== 'scheduled') { + return redirect()->route('podcast-view', [$podcast->id])->with( 'error', - lang('Podcast.messages.publishEditError') + lang('Podcast.messages.publishEditError'), ); } $rules = [ - 'publication_method' => 'required', - 'scheduled_publication_date' => - 'valid_date[Y-m-d H:i]|permit_empty', + 'publication_method' => 'required', + 'scheduled_publication_date' => 'valid_date[Y-m-d H:i]|permit_empty', ]; if (! $this->validate($rules)) { @@ -782,14 +765,16 @@ class PodcastController extends BaseController ->with('errors', $this->validator->getErrors()); } + $validData = $this->validator->getValidated(); + $db = db_connect(); $db->transStart(); - $publishMethod = $this->request->getPost('publication_method'); + $publishMethod = $validData['publication_method']; if ($publishMethod === 'schedule') { - $scheduledPublicationDate = $this->request->getPost('scheduled_publication_date'); + $scheduledPublicationDate = $validData['scheduled_publication_date']; if ($scheduledPublicationDate) { - $this->podcast->published_at = Time::createFromFormat( + $podcast->published_at = Time::createFromFormat( 'Y-m-d H:i', $scheduledPublicationDate, $this->request->getPost('client_timezone'), @@ -802,23 +787,23 @@ class PodcastController extends BaseController ->with('error', lang('Podcast.messages.scheduleDateError')); } } else { - $this->podcast->published_at = Time::now(); + $podcast->published_at = Time::now(); } - $post = (new PostModel()) + $post = new PostModel() ->where([ - 'actor_id' => $this->podcast->actor_id, + 'actor_id' => $podcast->actor_id, 'episode_id' => null, ]) ->first(); $newPostMessage = $this->request->getPost('message'); - if ($post !== null) { + if ($post instanceof Post) { if ($newPostMessage !== '') { // edit post if post exists and message is not empty $post->message = $newPostMessage; - $post->published_at = $this->podcast->published_at; + $post->published_at = $podcast->published_at; $postModel = new PostModel(); if (! $postModel->editPost($post)) { @@ -833,7 +818,7 @@ class PodcastController extends BaseController $postModel = new PostModel(); $post = $postModel ->where([ - 'actor_id' => $this->podcast->actor_id, + 'actor_id' => $podcast->actor_id, 'episode_id' => null, ]) ->first(); @@ -842,12 +827,12 @@ class PodcastController extends BaseController } elseif ($newPostMessage !== '') { // create post if there is no post and message is not empty $newPost = new Post([ - 'actor_id' => $this->podcast->actor_id, - 'message' => $newPostMessage, + 'actor_id' => $podcast->actor_id, + 'message' => $newPostMessage, 'created_by' => user_id(), ]); - $newPost->published_at = $this->podcast->published_at; + $newPost->published_at = $podcast->published_at; $postModel = new PostModel(); if (! $postModel->addPost($newPost)) { @@ -859,13 +844,13 @@ class PodcastController extends BaseController } } - $episodes = (new EpisodeModel()) - ->where('podcast_id', $this->podcast->id) - ->where('published_at !=', null) + $episodes = new EpisodeModel() + ->where('podcast_id', $podcast->id) + ->where('published_at !=') ->findAll(); foreach ($episodes as $episode) { - $episode->published_at = $this->podcast->published_at->addSeconds(1); + $episode->published_at = $podcast->published_at->addSeconds(1); $episodeModel = new EpisodeModel(); if (! $episodeModel->update($episode->id, $episode)) { @@ -876,10 +861,11 @@ class PodcastController extends BaseController ->with('errors', $episodeModel->errors()); } - $post = (new PostModel())->where('episode_id', $episode->id) + $post = new PostModel() + ->where('episode_id', $episode->id) ->first(); - if ($post !== null) { + if ($post instanceof Post) { $post->published_at = $episode->published_at; $postModel = new PostModel(); if (! $postModel->update($post->id, $post)) { @@ -893,7 +879,7 @@ class PodcastController extends BaseController } $podcastModel = new PodcastModel(); - if (! $podcastModel->update($this->podcast->id, $this->podcast)) { + if (! $podcastModel->update($podcast->id, $podcast)) { $db->transRollback(); return redirect() ->back() @@ -903,13 +889,13 @@ class PodcastController extends BaseController $db->transComplete(); - return redirect()->route('podcast-view', [$this->podcast->id]); + return redirect()->route('podcast-view', [$podcast->id]); } - public function publishCancel(): RedirectResponse + public function publishCancelAction(Podcast $podcast): RedirectResponse { - if ($this->podcast->publication_status !== 'scheduled') { - return redirect()->route('podcast-view', [$this->podcast->id]); + if ($podcast->publication_status !== 'scheduled') { + return redirect()->route('podcast-view', [$podcast->id]); } $db = db_connect(); @@ -918,17 +904,17 @@ class PodcastController extends BaseController $postModel = new PostModel(); $post = $postModel ->where([ - 'actor_id' => $this->podcast->actor_id, + 'actor_id' => $podcast->actor_id, 'episode_id' => null, ]) ->first(); - if ($post !== null) { + if ($post instanceof Post) { $postModel->removePost($post); } - $episodes = (new EpisodeModel()) - ->where('podcast_id', $this->podcast->id) - ->where('published_at !=', null) + $episodes = new EpisodeModel() + ->where('podcast_id', $podcast->id) + ->where('published_at !=') ->findAll(); foreach ($episodes as $episode) { @@ -949,10 +935,10 @@ class PodcastController extends BaseController $postModel->removePost($post); } - $this->podcast->published_at = null; + $podcast->published_at = null; $podcastModel = new PodcastModel(); - if (! $podcastModel->update($this->podcast->id, $this->podcast)) { + if (! $podcastModel->update($podcast->id, $podcast)) { $db->transRollback(); return redirect() ->back() @@ -962,9 +948,9 @@ class PodcastController extends BaseController $db->transComplete(); - return redirect()->route('podcast-view', [$this->podcast->id])->with( + return redirect()->route('podcast-view', [$podcast->id])->with( 'message', - lang('Podcast.messages.publishCancelSuccess') + lang('Podcast.messages.publishCancelSuccess'), ); } } diff --git a/modules/Admin/Controllers/PodcastImportController.php b/modules/Admin/Controllers/PodcastImportController.php deleted file mode 100644 index 39b0f09a..00000000 --- a/modules/Admin/Controllers/PodcastImportController.php +++ /dev/null @@ -1,706 +0,0 @@ -{$method}(); - } - - if (($this->podcast = (new PodcastModel())->getPodcastById((int) $params[0])) !== null) { - return $this->{$method}(); - } - - throw PageNotFoundException::forPageNotFound(); - } - - public function index(): string - { - helper(['form', 'misc']); - - $languageOptions = (new LanguageModel())->getLanguageOptions(); - $categoryOptions = (new CategoryModel())->getCategoryOptions(); - - $data = [ - 'languageOptions' => $languageOptions, - 'categoryOptions' => $categoryOptions, - 'browserLang' => get_browser_language($this->request->getServer('HTTP_ACCEPT_LANGUAGE')), - ]; - - return view('podcast/import', $data); - } - - public function attemptImport(): RedirectResponse - { - helper(['media', 'misc']); - - $rules = [ - 'handle' => 'required|regex_match[/^[a-zA-Z0-9\_]{1,32}$/]', - 'imported_feed_url' => 'required|validate_url', - 'season_number' => 'is_natural_no_zero|permit_empty', - 'max_episodes' => 'is_natural_no_zero|permit_empty', - ]; - - if (! $this->validate($rules)) { - return redirect() - ->back() - ->withInput() - ->with('errors', $this->validator->getErrors()); - } - - try { - ini_set('user_agent', 'Castopod/' . CP_VERSION); - $feed = simplexml_load_file($this->request->getPost('imported_feed_url')); - } catch (ErrorException $errorException) { - return redirect() - ->back() - ->withInput() - ->with('errors', [ - $errorException->getMessage() . - ': ' . - $this->request->getPost('imported_feed_url') . - ' ⎋', - ]); - } - - $nsItunes = $feed->channel[0]->children('http://www.itunes.com/dtds/podcast-1.0.dtd'); - $nsPodcast = $feed->channel[0]->children( - 'https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md', - ); - $nsContent = $feed->channel[0]->children('http://purl.org/rss/1.0/modules/content/'); - - if ((string) $nsPodcast->locked === 'yes') { - return redirect() - ->back() - ->withInput() - ->with('errors', [lang('PodcastImport.lock_import')]); - } - - $converter = new HtmlConverter(); - - $channelDescriptionHtml = (string) $feed->channel[0]->description; - - try { - if ( - property_exists($nsItunes, 'image') && $nsItunes->image !== null && - $nsItunes->image->attributes()['href'] !== null - ) { - $coverFile = download_file((string) $nsItunes->image->attributes()['href']); - } else { - $coverFile = download_file((string) $feed->channel[0]->image->url); - } - - $location = null; - if (property_exists($nsPodcast, 'location') && $nsPodcast->location !== null) { - $location = new Location( - (string) $nsPodcast->location, - $nsPodcast->location->attributes()['geo'] === null ? null : (string) $nsPodcast->location->attributes()['geo'], - $nsPodcast->location->attributes()['osm'] === null ? null : (string) $nsPodcast->location->attributes()['osm'], - ); - } - - $guid = null; - if (property_exists($nsPodcast, 'guid') && $nsPodcast->guid !== null) { - $guid = (string) $nsPodcast->guid; - } - - $db = db_connect(); - $db->transStart(); - - $podcast = new Podcast([ - 'guid' => $guid, - 'handle' => $this->request->getPost('handle'), - 'imported_feed_url' => $this->request->getPost('imported_feed_url'), - 'new_feed_url' => url_to('podcast-rss-feed', $this->request->getPost('handle')), - 'title' => (string) $feed->channel[0]->title, - 'description_markdown' => $converter->convert($channelDescriptionHtml), - 'description_html' => $channelDescriptionHtml, - 'cover' => $coverFile, - 'banner' => null, - 'language_code' => $this->request->getPost('language'), - 'category_id' => $this->request->getPost('category'), - 'parental_advisory' => - property_exists($nsItunes, 'explicit') && $nsItunes->explicit !== null - ? (in_array((string) $nsItunes->explicit, ['yes', 'true'], true) - ? 'explicit' - : (in_array((string) $nsItunes->explicit, ['no', 'false'], true) - ? 'clean' - : null)) - : null, - 'owner_name' => (string) $nsItunes->owner->name, - 'owner_email' => (string) $nsItunes->owner->email, - 'publisher' => (string) $nsItunes->author, - 'type' => property_exists( - $nsItunes, - 'type' - ) && $nsItunes->type !== null ? (string) $nsItunes->type : 'episodic', - 'copyright' => (string) $feed->channel[0]->copyright, - 'is_blocked' => - property_exists($nsItunes, 'block') && $nsItunes->block !== null && (string) $nsItunes->block === 'yes', - 'is_completed' => - property_exists( - $nsItunes, - 'complete' - ) && $nsItunes->complete !== null && (string) $nsItunes->complete === 'yes', - 'location' => $location, - 'created_by' => user_id(), - 'updated_by' => user_id(), - ]); - } catch (ErrorException $ex) { - return redirect() - ->back() - ->withInput() - ->with('errors', [ - $ex->getMessage() . - ': ' . - $this->request->getPost('imported_feed_url') . - ' ⎋', - ]); - } - - $podcastModel = new PodcastModel(); - if (! ($newPodcastId = $podcastModel->insert($podcast, true))) { - $db->transRollback(); - return redirect() - ->back() - ->withInput() - ->with('errors', $podcastModel->errors()); - } - - $authorize = Services::authorization(); - $podcastAdminGroup = $authorize->group('podcast_admin'); - - $podcastModel->addPodcastContributor(user_id(), $newPodcastId, (int) $podcastAdminGroup->id); - - $podcastsPlatformsData = []; - $platformTypes = [ - [ - 'name' => 'podcasting', - 'elements' => $nsPodcast->id, - 'account_url_key' => 'url', - 'account_id_key' => 'id', - ], - [ - 'name' => 'social', - 'elements' => $nsPodcast->social, - 'account_url_key' => 'accountUrl', - 'account_id_key' => 'accountId', - ], - [ - 'name' => 'funding', - 'elements' => $nsPodcast->funding, - 'account_url_key' => 'url', - 'account_id_key' => 'id', - ], - ]; - $platformModel = new PlatformModel(); - foreach ($platformTypes as $platformType) { - foreach ($platformType['elements'] as $platform) { - $platformLabel = $platform->attributes()['platform']; - $platformSlug = slugify((string) $platformLabel); - if ($platformModel->getPlatform($platformSlug) !== null) { - $podcastsPlatformsData[] = [ - 'platform_slug' => $platformSlug, - 'podcast_id' => $newPodcastId, - 'link_url' => $platform->attributes()[$platformType['account_url_key']], - 'account_id' => $platform->attributes()[$platformType['account_id_key']], - 'is_visible' => false, - ]; - } - } - } - - if (count($podcastsPlatformsData) > 1) { - $platformModel->createPodcastPlatforms($newPodcastId, $podcastsPlatformsData); - } - - foreach ($nsPodcast->person as $podcastPerson) { - $fullName = (string) $podcastPerson; - $personModel = new PersonModel(); - $newPersonId = null; - if (($newPerson = $personModel->getPerson($fullName)) !== null) { - $newPersonId = $newPerson->id; - } else { - $newPodcastPerson = new Person([ - 'full_name' => $fullName, - 'unique_name' => slugify($fullName), - 'information_url' => $podcastPerson->attributes()['href'], - 'avatar' => download_file((string) $podcastPerson->attributes()['img']), - 'created_by' => user_id(), - 'updated_by' => user_id(), - ]); - - if (! $newPersonId = $personModel->insert($newPodcastPerson)) { - return redirect() - ->back() - ->withInput() - ->with('errors', $personModel->errors()); - } - } - - // TODO: these checks should be in the taxonomy as default values - $podcastPersonGroup = $podcastPerson->attributes()['group'] ?? 'Cast'; - $podcastPersonRole = $podcastPerson->attributes()['role'] ?? 'Host'; - - $personGroup = ReversedTaxonomy::$taxonomy[(string) $podcastPersonGroup]; - - $personGroupSlug = $personGroup['slug']; - $personRoleSlug = $personGroup['roles'][(string) $podcastPersonRole]['slug']; - - $podcastPersonModel = new PersonModel(); - if (! $podcastPersonModel->addPodcastPerson( - $newPodcastId, - $newPersonId, - $personGroupSlug, - $personRoleSlug - )) { - return redirect() - ->back() - ->withInput() - ->with('errors', $podcastPersonModel->errors()); - } - } - - $itemsCount = $feed->channel[0]->item->count(); - - $lastItem = - $this->request->getPost('max_episodes') !== '' && - $this->request->getPost('max_episodes') < $itemsCount - ? (int) $this->request->getPost('max_episodes') - : $itemsCount; - - $slugs = []; - for ($itemNumber = 1; $itemNumber <= $lastItem; ++$itemNumber) { - $item = $feed->channel[0]->item[$itemsCount - $itemNumber]; - - $nsItunes = $item->children('http://www.itunes.com/dtds/podcast-1.0.dtd'); - $nsPodcast = $item->children( - 'https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md', - ); - $nsContent = $item->children('http://purl.org/rss/1.0/modules/content/'); - - $textToSlugify = $this->request->getPost('slug_field') === 'title' - ? (string) $item->title - : basename((string) $item->link); - $slug = slugify($textToSlugify, 120); - if (in_array($slug, $slugs, true)) { - $slugNumber = 2; - while (in_array($slug . '-' . $slugNumber, $slugs, true)) { - ++$slugNumber; - } - - $slug = $slug . '-' . $slugNumber; - } - - $slugs[] = $slug; - $itemDescriptionHtml = match ($this->request->getPost('description_field')) { - 'content' => (string) $nsContent->encoded, - 'summary' => (string) $nsItunes->summary, - 'subtitle_summary' => $nsItunes->subtitle . '
    ' . $nsItunes->summary, - default => (string) $item->description, - }; - - if ( - property_exists($nsItunes, 'image') && $nsItunes->image !== null && - $nsItunes->image->attributes()['href'] !== null - ) { - $episodeCover = download_file((string) $nsItunes->image->attributes()['href']); - } else { - $episodeCover = null; - } - - $location = null; - if (property_exists($nsPodcast, 'location') && $nsPodcast->location !== null) { - $location = new Location( - (string) $nsPodcast->location, - $nsPodcast->location->attributes()['geo'] === null ? null : (string) $nsPodcast->location->attributes()['geo'], - $nsPodcast->location->attributes()['osm'] === null ? null : (string) $nsPodcast->location->attributes()['osm'], - ); - } - - $newEpisode = new Episode([ - 'podcast_id' => $newPodcastId, - 'title' => $item->title, - 'slug' => $slug, - 'guid' => $item->guid ?? null, - 'audio' => download_file( - (string) $item->enclosure->attributes()['url'], - (string) $item->enclosure->attributes()['type'] - ), - 'description_markdown' => $converter->convert($itemDescriptionHtml), - 'description_html' => $itemDescriptionHtml, - 'cover' => $episodeCover, - 'parental_advisory' => - property_exists($nsItunes, 'explicit') && $nsItunes->explicit !== null - ? (in_array((string) $nsItunes->explicit, ['yes', 'true'], true) - ? 'explicit' - : (in_array((string) $nsItunes->explicit, ['no', 'false'], true) - ? 'clean' - : null)) - : null, - 'number' => - $this->request->getPost('force_renumber') === 'yes' - ? $itemNumber - : ((string) $nsItunes->episode === '' ? null : (int) $nsItunes->episode), - 'season_number' => - $this->request->getPost('season_number') === '' - ? ((string) $nsItunes->season === '' ? null : (int) $nsItunes->season) - : (int) $this->request->getPost('season_number'), - 'type' => property_exists($nsItunes, 'episodeType') && in_array( - $nsItunes->episodeType, - ['trailer', 'full', 'bonus'], - true - ) - ? (string) $nsItunes->episodeType - : 'full', - 'is_blocked' => property_exists( - $nsItunes, - 'block' - ) && $nsItunes->block !== null && (string) $nsItunes->block === 'yes', - 'location' => $location, - 'created_by' => user_id(), - 'updated_by' => user_id(), - 'published_at' => strtotime((string) $item->pubDate), - ]); - - $episodeModel = new EpisodeModel(); - - if (! ($newEpisodeId = $episodeModel->insert($newEpisode, true))) { - // FIXME: What shall we do? - return redirect() - ->back() - ->withInput() - ->with('errors', $episodeModel->errors()); - } - - foreach ($nsPodcast->person as $episodePerson) { - $fullName = (string) $episodePerson; - $personModel = new PersonModel(); - $newPersonId = null; - if (($newPerson = $personModel->getPerson($fullName)) !== null) { - $newPersonId = $newPerson->id; - } else { - $newPerson = new Person([ - 'full_name' => $fullName, - 'unique_name' => slugify($fullName), - 'information_url' => $episodePerson->attributes()['href'], - 'avatar' => download_file((string) $episodePerson->attributes()['img']), - 'created_by' => user_id(), - 'updated_by' => user_id(), - ]); - - if (! ($newPersonId = $personModel->insert($newPerson))) { - return redirect() - ->back() - ->withInput() - ->with('errors', $personModel->errors()); - } - } - - // TODO: these checks should be in the taxonomy as default values - $episodePersonGroup = $episodePerson->attributes()['group'] ?? 'Cast'; - $episodePersonRole = $episodePerson->attributes()['role'] ?? 'Host'; - - $personGroup = ReversedTaxonomy::$taxonomy[(string) $episodePersonGroup]; - - $personGroupSlug = $personGroup['slug']; - $personRoleSlug = $personGroup['roles'][(string) $episodePersonRole]['slug']; - - $episodePersonModel = new PersonModel(); - if (! $episodePersonModel->addEpisodePerson( - $newPodcastId, - $newEpisodeId, - $newPersonId, - $personGroupSlug, - $personRoleSlug - )) { - return redirect() - ->back() - ->withInput() - ->with('errors', $episodePersonModel->errors()); - } - } - - if ($itemNumber === 1) { - $firstEpisodePublicationDate = strtotime((string) $item->pubDate); - } - } - - // set interact as the newly imported podcast actor - $importedPodcast = (new PodcastModel())->getPodcastById($newPodcastId); - set_interact_as_actor($importedPodcast->actor_id); - - // set podcast publication date - $importedPodcast->published_at = $firstEpisodePublicationDate ?? $importedPodcast->created_at; - $podcastModel = new PodcastModel(); - if (! $podcastModel->update($importedPodcast->id, $importedPodcast)) { - $db->transRollback(); - return redirect() - ->back() - ->withInput() - ->with('errors', $podcastModel->errors()); - } - - $db->transComplete(); - - return redirect()->route('podcast-view', [$newPodcastId]); - } - - public function updateImport(): RedirectResponse - { - if ($this->podcast->imported_feed_url === null) { - return redirect() - ->back() - ->with('error', lang('Podcast.messages.podcastNotImported')); - } - - try { - ini_set('user_agent', 'Castopod/' . CP_VERSION); - $feed = simplexml_load_file($this->podcast->imported_feed_url); - } catch (ErrorException $errorException) { - return redirect() - ->back() - ->withInput() - ->with('errors', [ - $errorException->getMessage() . - ': ' . - $this->podcast->imported_feed_url . - ' ⎋', - ]); - } - - $nsPodcast = $feed->channel[0]->children( - 'https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md', - ); - - if ((string) $nsPodcast->locked === 'yes') { - return redirect() - ->back() - ->withInput() - ->with('errors', [lang('PodcastImport.lock_import')]); - } - - $itemsCount = $feed->channel[0]->item->count(); - - $lastItem = $itemsCount; - - $lastEpisode = (new EpisodeModel())->where('podcast_id', $this->podcast->id) - ->orderBy('created_at', 'desc') - ->first(); - - if ($lastEpisode !== null) { - for ($itemNumber = 0; $itemNumber < $itemsCount; ++$itemNumber) { - $item = $feed->channel[0]->item[$itemNumber]; - - if (property_exists( - $item, - 'guid' - ) && $item->guid !== null && $lastEpisode->guid === (string) $item->guid) { - $lastItem = $itemNumber; - break; - } - } - } - - if ($lastItem === 0) { - return redirect() - ->back() - ->with('message', lang('Podcast.messages.podcastFeedUpToDate')); - } - - helper(['media', 'misc']); - - $converter = new HtmlConverter(); - - $slugs = []; - - $db = db_connect(); - $db->transStart(); - - for ($itemNumber = 1; $itemNumber <= $lastItem; ++$itemNumber) { - $item = $feed->channel[0]->item[$lastItem - $itemNumber]; - - $nsItunes = $item->children('http://www.itunes.com/dtds/podcast-1.0.dtd'); - $nsPodcast = $item->children( - 'https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md', - ); - - $textToSlugify = (string) $item->title; - $slug = slugify($textToSlugify, 120); - if (in_array($slug, $slugs, true) || (new EpisodeModel())->where([ - 'slug' => $slug, - 'podcast_id' => $this->podcast->id, - ])->first()) { - $slugNumber = 2; - while (in_array($slug . '-' . $slugNumber, $slugs, true) || (new EpisodeModel())->where([ - 'slug' => $slug . '-' . $slugNumber, - 'podcast_id' => $this->podcast->id, - ])->first()) { - ++$slugNumber; - } - - $slug = $slug . '-' . $slugNumber; - } - - $slugs[] = $slug; - - $itemDescriptionHtml = (string) $item->description; - - if ( - property_exists($nsItunes, 'image') && $nsItunes->image !== null && - $nsItunes->image->attributes()['href'] !== null - ) { - $episodeCover = download_file((string) $nsItunes->image->attributes()['href']); - } else { - $episodeCover = null; - } - - $location = null; - if (property_exists($nsPodcast, 'location') && $nsPodcast->location !== null) { - $location = new Location( - (string) $nsPodcast->location, - $nsPodcast->location->attributes()['geo'] === null ? null : (string) $nsPodcast->location->attributes()['geo'], - $nsPodcast->location->attributes()['osm'] === null ? null : (string) $nsPodcast->location->attributes()['osm'], - ); - } - - $newEpisode = new Episode([ - 'podcast_id' => $this->podcast->id, - 'title' => $item->title, - 'slug' => $slug, - 'guid' => $item->guid ?? null, - 'audio' => download_file( - (string) $item->enclosure->attributes()['url'], - (string) $item->enclosure->attributes()['type'] - ), - 'description_markdown' => $converter->convert($itemDescriptionHtml), - 'description_html' => $itemDescriptionHtml, - 'cover' => $episodeCover, - 'parental_advisory' => - property_exists($nsItunes, 'explicit') && $nsItunes->explicit !== null - ? (in_array((string) $nsItunes->explicit, ['yes', 'true'], true) - ? 'explicit' - : (in_array((string) $nsItunes->explicit, ['no', 'false'], true) - ? 'clean' - : null)) - : null, - 'number' => ((string) $nsItunes->episode === '' ? null : (int) $nsItunes->episode), - 'season_number' => ((string) $nsItunes->season === '' ? null : (int) $nsItunes->season), - 'type' => property_exists($nsItunes, 'episodeType') && $nsItunes->episodeType !== null - ? (string) $nsItunes->episodeType - : 'full', - 'is_blocked' => property_exists( - $nsItunes, - 'block' - ) && $nsItunes->block !== null && (string) $nsItunes->block === 'yes', - 'location' => $location, - 'created_by' => user_id(), - 'updated_by' => user_id(), - 'published_at' => strtotime((string) $item->pubDate), - ]); - - $episodeModel = new EpisodeModel(); - - if (! ($newEpisodeId = $episodeModel->insert($newEpisode, true))) { - // FIXME: What shall we do? - return redirect() - ->back() - ->withInput() - ->with('errors', $episodeModel->errors()); - } - - foreach ($nsPodcast->person as $episodePerson) { - $fullName = (string) $episodePerson; - $personModel = new PersonModel(); - $newPersonId = null; - if (($newPerson = $personModel->getPerson($fullName)) !== null) { - $newPersonId = $newPerson->id; - } else { - $newPerson = new Person([ - 'full_name' => $fullName, - 'unique_name' => slugify($fullName), - 'information_url' => $episodePerson->attributes()['href'], - 'avatar' => download_file((string) $episodePerson->attributes()['img']), - 'created_by' => user_id(), - 'updated_by' => user_id(), - ]); - - if (! ($newPersonId = $personModel->insert($newPerson))) { - return redirect() - ->back() - ->withInput() - ->with('errors', $personModel->errors()); - } - } - - // TODO: these checks should be in the taxonomy as default values - $episodePersonGroup = $episodePerson->attributes()['group'] ?? 'Cast'; - $episodePersonRole = $episodePerson->attributes()['role'] ?? 'Host'; - - $personGroup = ReversedTaxonomy::$taxonomy[(string) $episodePersonGroup]; - - $personGroupSlug = $personGroup['slug']; - $personRoleSlug = $personGroup['roles'][(string) $episodePersonRole]['slug']; - - $episodePersonModel = new PersonModel(); - if (! $episodePersonModel->addEpisodePerson( - $this->podcast->id, - $newEpisodeId, - $newPersonId, - $personGroupSlug, - $personRoleSlug - )) { - return redirect() - ->back() - ->withInput() - ->with('errors', $episodePersonModel->errors()); - } - } - } - - $db->transComplete(); - - return redirect()->route('podcast-view', [$this->podcast->id])->with( - 'message', - lang('Podcast.messages.podcastFeedUpdateSuccess', [ - 'number_of_new_episodes' => $lastItem, - ]) - ); - } -} diff --git a/modules/Admin/Controllers/PodcastPersonController.php b/modules/Admin/Controllers/PodcastPersonController.php index 722ba28a..7713bbfc 100644 --- a/modules/Admin/Controllers/PodcastPersonController.php +++ b/modules/Admin/Controllers/PodcastPersonController.php @@ -27,32 +27,37 @@ class PodcastPersonController extends BaseController } if ( - ($this->podcast = (new PodcastModel())->getPodcastById((int) $params[0])) !== null + ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast ) { unset($params[0]); - return $this->{$method}(...$params); + return $this->{$method}($podcast, ...$params); } throw PageNotFoundException::forPageNotFound(); } - public function index(): string + public function index(Podcast $podcast): string { helper('form'); $data = [ - 'podcast' => $this->podcast, - 'podcastPersons' => (new PersonModel())->getPodcastPersons($this->podcast->id), - 'personOptions' => (new PersonModel())->getPersonOptions(), - 'taxonomyOptions' => (new PersonModel())->getTaxonomyOptions(), + 'podcast' => $podcast, + 'podcastPersons' => new PersonModel() + ->getPodcastPersons($podcast->id), + 'personOptions' => new PersonModel() + ->getPersonOptions(), + 'taxonomyOptions' => new PersonModel() + ->getTaxonomyOptions(), ]; + + $this->setHtmlHead(lang('Person.podcast_form.title')); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $podcast->at_handle, ]); return view('podcast/persons', $data); } - public function attemptAdd(): RedirectResponse + public function createAction(Podcast $podcast): RedirectResponse { $rules = [ 'persons' => 'required', @@ -65,18 +70,18 @@ class PodcastPersonController extends BaseController ->with('errors', $this->validator->getErrors()); } - (new PersonModel())->addPodcastPersons( - $this->podcast->id, - $this->request->getPost('persons'), - $this->request->getPost('roles') ?? [], - ); + $validData = $this->validator->getValidated(); + + new PersonModel() + ->addPodcastPersons($podcast->id, $validData['persons'], $this->request->getPost('roles') ?? []); return redirect()->back(); } - public function remove(string $personId): RedirectResponse + public function deleteAction(Podcast $podcast, string $personId): RedirectResponse { - (new PersonModel())->removePersonFromPodcast($this->podcast->id, (int) $personId); + new PersonModel() + ->removePersonFromPodcast($podcast->id, (int) $personId); return redirect()->back(); } diff --git a/modules/Admin/Controllers/PodcastPlatformController.php b/modules/Admin/Controllers/PodcastPlatformController.php deleted file mode 100644 index 08b8f961..00000000 --- a/modules/Admin/Controllers/PodcastPlatformController.php +++ /dev/null @@ -1,110 +0,0 @@ -{$method}(); - } - - if ( - ($this->podcast = (new PodcastModel())->getPodcastById((int) $params[0])) !== null - ) { - unset($params[0]); - return $this->{$method}(...$params); - } - - throw PageNotFoundException::forPageNotFound(); - } - - public function index(): string - { - return view('podcast/platforms\dashboard'); - } - - public function platforms(string $platformType): string - { - helper('form'); - - $data = [ - 'podcast' => $this->podcast, - 'platformType' => $platformType, - 'platforms' => (new PlatformModel())->getPlatformsWithLinks($this->podcast->id, $platformType), - ]; - - replace_breadcrumb_params([ - 0 => $this->podcast->title, - ]); - - return view('podcast/platforms', $data); - } - - public function attemptPlatformsUpdate(string $platformType): RedirectResponse - { - $platformModel = new PlatformModel(); - $validation = Services::validation(); - - $podcastsPlatformsData = []; - - foreach ( - $this->request->getPost('platforms') - as $platformSlug => $podcastPlatform - ) { - $podcastPlatformUrl = $podcastPlatform['url']; - if ($podcastPlatformUrl === null) { - continue; - } - - if (! $validation->check($podcastPlatformUrl, 'validate_url')) { - continue; - } - - $podcastsPlatformsData[] = [ - 'platform_slug' => $platformSlug, - 'podcast_id' => $this->podcast->id, - 'link_url' => $podcastPlatformUrl, - 'account_id' => $podcastPlatform['account_id'] === '' ? null : $podcastPlatform['account_id'], - 'is_visible' => - array_key_exists('visible', $podcastPlatform) && - $podcastPlatform['visible'] === 'yes', - 'is_on_embed' => - array_key_exists('on_embed', $podcastPlatform,) && $podcastPlatform['on_embed'] === 'yes', - ]; - } - - $platformModel->savePodcastPlatforms($this->podcast->id, $platformType, $podcastsPlatformsData); - - return redirect() - ->back() - ->with('message', lang('Platforms.messages.updateSuccess')); - } - - public function removePodcastPlatform(string $platformSlug): RedirectResponse - { - (new PlatformModel())->removePodcastPlatform($this->podcast->id, $platformSlug); - - return redirect() - ->back() - ->with('message', lang('Platforms.messages.removeLinkSuccess')); - } -} diff --git a/modules/Admin/Controllers/SettingsController.php b/modules/Admin/Controllers/SettingsController.php index 8fd936f4..f0e8c508 100644 --- a/modules/Admin/Controllers/SettingsController.php +++ b/modules/Admin/Controllers/SettingsController.php @@ -10,15 +10,19 @@ declare(strict_types=1); namespace Modules\Admin\Controllers; +use App\Entities\Podcast; use App\Models\ActorModel; use App\Models\EpisodeCommentModel; use App\Models\EpisodeModel; -use App\Models\MediaModel; use App\Models\PersonModel; use App\Models\PodcastModel; use App\Models\PostModel; use CodeIgniter\Files\File; +use CodeIgniter\HTTP\Files\UploadedFile; use CodeIgniter\HTTP\RedirectResponse; +use Modules\Media\Entities\Audio; +use Modules\Media\FileManagers\FileManagerInterface; +use Modules\Media\Models\MediaModel; use PHP_ICO; class SettingsController extends BaseController @@ -26,14 +30,14 @@ class SettingsController extends BaseController public function index(): string { helper('form'); + $this->setHtmlHead(lang('Settings.title')); return view('settings/general'); } - public function attemptInstanceEdit(): RedirectResponse + public function instanceEditAction(): RedirectResponse { $rules = [ - 'site_icon' => - 'is_image[site_icon]|ext_in[site_icon,png,jpeg]|is_image_ratio[site_icon,1,1]|min_dims[image,512,512]|permit_empty', + 'site_icon' => 'is_image[site_icon]|ext_in[site_icon,png,jpeg]|is_image_ratio[site_icon,1,1]|min_dims[image,512,512]|permit_empty', ]; if (! $this->validate($rules)) { @@ -54,56 +58,74 @@ class SettingsController extends BaseController } $siteIconFile = $this->request->getFile('site_icon'); - if ($siteIconFile !== null && $siteIconFile->isValid()) { - helper(['filesystem', 'media']); + if ($siteIconFile instanceof UploadedFile && $siteIconFile->isValid()) { + /** @var FileManagerInterface $fileManager */ + $fileManager = service('file_manager'); // delete site folder in media before repopulating it - delete_files(media_path('/site')); - - // save original in disk - $originalFilename = save_media($siteIconFile, 'site', 'icon'); + $fileManager->deleteAll('site'); // convert jpeg image to png if not if ($siteIconFile->getClientMimeType() !== 'image/png') { - service('image')->withFile(media_path($originalFilename)) + $tempFilePath = tempnam(WRITEPATH . 'temp', 'img_'); + service('image') + ->withFile($siteIconFile->getRealPath()) ->convert(IMAGETYPE_JPEG) - ->save(media_path('/site/icon.png')); + ->save($tempFilePath); + + @unlink($siteIconFile->getRealPath()); + + $siteIconFile = new File($tempFilePath, true); } + $icoTempFilePath = WRITEPATH . 'temp/img_favicon.ico'; + + // generate ico + $ico_lib = new PHP_ICO(); + $ico_lib->add_image($siteIconFile->getRealPath(), [[32, 32], [64, 64]]); + $ico_lib->save_ico($icoTempFilePath); + // generate random hash to use as a suffix to renew browser cache $randomHash = substr(bin2hex(random_bytes(18)), 0, 8); - // generate ico - $ico_lib = new PHP_ICO(); - $ico_lib->add_image(media_path('/site/icon.png'), [[32, 32], [64, 64]]); - $ico_lib->save_ico(media_path("/site/favicon.{$randomHash}.ico")); + // save ico + $fileManager->save(new File($icoTempFilePath, true), "site/favicon.{$randomHash}.ico"); // resize original to needed sizes foreach ([64, 180, 192, 512] as $size) { + $tempFilePath = tempnam(WRITEPATH . 'temp', 'img_'); service('image') - ->withFile(media_path('/site/icon.png')) + ->withFile($siteIconFile->getRealPath()) ->resize($size, $size) - ->save(media_path("/site/icon-{$size}.{$randomHash}.png")); + ->save($tempFilePath); + + // save sizes to + $fileManager->save(new File($tempFilePath), "site/icon-{$size}.{$randomHash}.png"); } + // save original as png + $fileManager->save($siteIconFile, 'site/icon.png'); + service('settings') ->set('App.siteIcon', [ - 'ico' => '/' . media_path("/site/favicon.{$randomHash}.ico"), - '64' => '/' . media_path("/site/icon-64.{$randomHash}.png"), - '180' => '/' . media_path("/site/icon-180.{$randomHash}.png"), - '192' => '/' . media_path("/site/icon-192.{$randomHash}.png"), - '512' => '/' . media_path("/site/icon-512.{$randomHash}.png"), + 'ico' => "site/favicon.{$randomHash}.ico", + '64' => "site/icon-64.{$randomHash}.png", + '180' => "site/icon-180.{$randomHash}.png", + '192' => "site/icon-192.{$randomHash}.png", + '512' => "site/icon-512.{$randomHash}.png", ]); } return redirect('settings-general')->with('message', lang('Settings.instance.editSuccess')); } - public function deleteIcon(): RedirectResponse + public function deleteIconAction(): RedirectResponse { - helper(['filesystem', 'media']); - // delete site folder in media - delete_files(media_path('/site')); + /** @var FileManagerInterface $fileManager */ + $fileManager = service('file_manager'); + + // delete site folder + $fileManager->deleteAll('site'); service('settings') ->forget('App.siteIcon'); @@ -111,25 +133,17 @@ class SettingsController extends BaseController return redirect('settings-general')->with('message', lang('Settings.instance.deleteIconSuccess')); } - public function regenerateImages(): RedirectResponse + public function regenerateImagesAction(): RedirectResponse { - helper('media'); + /** @var Podcast[] $allPodcasts */ + $allPodcasts = new PodcastModel() + ->findAll(); - $allPodcasts = (new PodcastModel())->findAll(); - $imageExt = ['jpg', 'png', 'webp']; + /** @var FileManagerInterface $fileManager */ + $fileManager = service('file_manager'); foreach ($allPodcasts as $podcast) { - foreach ($imageExt as $ext) { - $podcastImages = glob(media_path("/podcasts/{$podcast->handle}/*_*{$ext}")); - - if ($podcastImages) { - foreach ($podcastImages as $podcastImage) { - if (is_file($podcastImage)) { - unlink($podcastImage); - } - } - } - } + $fileManager->deletePodcastImageSizes($podcast->handle); $podcast->cover->saveSizes(); if ($podcast->banner_id !== null) { @@ -143,18 +157,10 @@ class SettingsController extends BaseController } } - foreach ($imageExt as $ext) { - $personsImages = glob(media_path("/persons/*_*{$ext}")); - if ($personsImages) { - foreach ($personsImages as $personsImage) { - if (is_file($personsImage)) { - unlink($personsImage); - } - } - } - } + $fileManager->deletePersonImagesSizes(); - $persons = (new PersonModel())->findAll(); + $persons = new PersonModel() + ->findAll(); foreach ($persons as $person) { if ($person->avatar_id !== null) { $person->avatar->saveSizes(); @@ -164,124 +170,30 @@ class SettingsController extends BaseController return redirect('settings-general')->with('message', lang('Settings.images.regenerationSuccess')); } - public function runHousekeeping(): RedirectResponse + public function housekeepingAction(): RedirectResponse { if ($this->request->getPost('reset_counts') === 'yes') { // recalculate fediverse counts - (new ActorModel())->resetFollowersCount(); - (new ActorModel())->resetPostsCount(); - (new PostModel())->setEpisodeIdForRepliesOfEpisodePosts(); - (new PostModel())->resetFavouritesCount(); - (new PostModel())->resetReblogsCount(); - (new PostModel())->resetRepliesCount(); - (new EpisodeModel())->resetCommentsCount(); - (new EpisodeModel())->resetPostsCount(); - (new EpisodeCommentModel())->resetLikesCount(); - (new EpisodeCommentModel())->resetRepliesCount(); - } - - helper('media'); - - if ($this->request->getPost('rewrite_media') === 'yes') { - $imageExt = ['jpg', 'png', 'webp']; - // Delete all podcast image sizes to recreate them - $allPodcasts = (new PodcastModel())->findAll(); - foreach ($allPodcasts as $podcast) { - foreach ($imageExt as $ext) { - $podcastImages = glob(media_path("/podcasts/{$podcast->handle}/*_*{$ext}")); - - if ($podcastImages) { - foreach ($podcastImages as $podcastImage) { - if (is_file($podcastImage)) { - unlink($podcastImage); - } - } - } - } - } - - // Delete all person image sizes to recreate them - foreach ($imageExt as $ext) { - $personsImages = glob(media_path("/persons/*_*{$ext}")); - if ($personsImages) { - foreach ($personsImages as $personsImage) { - if (is_file($personsImage)) { - unlink($personsImage); - } - } - } - } - - $allImages = (new MediaModel('image'))->getAllOfType(); - foreach ($allImages as $image) { - if (str_starts_with($image->file_path, 'podcasts')) { - if (str_ends_with($image->file_path, 'banner.jpg') || str_ends_with( - $image->file_path, - 'banner.png' - ) || str_ends_with($image->file_path, 'banner.jpeg')) { - $image->sizes = config('Images') - ->podcastBannerSizes; - } else { - $image->sizes = config('Images') - ->podcastCoverSizes; - } - } elseif (str_starts_with($image->file_path, 'persons')) { - $image->sizes = config('Images') - ->personAvatarSizes; - } else { - $image->sizes = []; - } - - $image->setFile(new File(media_path($image->file_path))); - - (new MediaModel('image'))->updateMedia($image); - } - - $allAudio = (new MediaModel('audio'))->getAllOfType(); - foreach ($allAudio as $audio) { - $audio->setFile(new File(media_path($audio->file_path))); - - (new MediaModel('audio'))->updateMedia($audio); - } - - $allTranscripts = (new MediaModel('transcript'))->getAllOfType(); - foreach ($allTranscripts as $transcript) { - $transcript->setFile(new File(media_path($transcript->file_path))); - - (new MediaModel('transcript'))->updateMedia($transcript); - } - - $allChapters = (new MediaModel('chapters'))->getAllOfType(); - foreach ($allChapters as $chapters) { - $chapters->setFile(new File(media_path($chapters->file_path))); - - (new MediaModel('chapters'))->updateMedia($chapters); - } - - $allVideos = (new MediaModel('video'))->getAllOfType(); - foreach ($allVideos as $video) { - $video->setFile(new File(media_path($video->file_path))); - - (new MediaModel('video'))->updateMedia($video); - } - - // reset avatar and banner image urls for each podcast actor - foreach ($allPodcasts as $podcast) { - $actorModel = new ActorModel(); - $actor = $actorModel->getActorById($podcast->actor_id); - - if ($actor !== null) { - // update values - $actor->avatar_image_url = $podcast->cover->federation_url; - $actor->avatar_image_mimetype = $podcast->cover->file_mimetype; - $actor->cover_image_url = $podcast->banner->federation_url; - $actor->cover_image_mimetype = $podcast->banner->file_mimetype; - - if ($actor->hasChanged()) { - $actorModel->update($actor->id, $actor); - } - } - } + new ActorModel() + ->resetFollowersCount(); + new ActorModel() + ->resetPostsCount(); + new PostModel() + ->setEpisodeIdForRepliesOfEpisodePosts(); + new PostModel() + ->resetFavouritesCount(); + new PostModel() + ->resetReblogsCount(); + new PostModel() + ->resetRepliesCount(); + new EpisodeModel() + ->resetCommentsCount(); + new EpisodeModel() + ->resetPostsCount(); + new EpisodeCommentModel() + ->resetLikesCount(); + new EpisodeCommentModel() + ->resetRepliesCount(); } if ($this->request->getPost('clear_cache') === 'yes') { @@ -289,7 +201,9 @@ class SettingsController extends BaseController } if ($this->request->getPost('rename_episodes_files') === 'yes') { - $allAudio = (new MediaModel('audio'))->getAllOfType(); + /** @var Audio[] $allAudio */ + $allAudio = new MediaModel('audio') + ->getAllOfType(); foreach ($allAudio as $audio) { $audio->rename(); @@ -299,13 +213,14 @@ class SettingsController extends BaseController return redirect('settings-general')->with('message', lang('Settings.housekeeping.runSuccess')); } - public function theme(): string + public function themeView(): string { helper('form'); + $this->setHtmlHead(lang('Settings.theme.title')); return view('settings/theme'); } - public function attemptSetInstanceTheme(): RedirectResponse + public function themeAction(): RedirectResponse { $theme = $this->request->getPost('theme'); service('settings') diff --git a/modules/Admin/Controllers/SoundbiteController.php b/modules/Admin/Controllers/SoundbiteController.php index 0016de24..2ca68bac 100644 --- a/modules/Admin/Controllers/SoundbiteController.php +++ b/modules/Admin/Controllers/SoundbiteController.php @@ -15,96 +15,89 @@ use App\Entities\Episode; use App\Entities\Podcast; use App\Models\ClipModel; use App\Models\EpisodeModel; -use App\Models\MediaModel; use App\Models\PodcastModel; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; +use Modules\Media\Models\MediaModel; class SoundbiteController extends BaseController { - protected Podcast $podcast; - - protected Episode $episode; - public function _remap(string $method, string ...$params): mixed { + if ($params === []) { + throw PageNotFoundException::forPageNotFound(); + } + + if (count($params) === 1) { + if (! ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast) { + throw PageNotFoundException::forPageNotFound(); + } + + return $this->{$method}($podcast); + } + if ( - ($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) === null + ! ($episode = new EpisodeModel()->getEpisodeById((int) $params[1])) instanceof Episode ) { throw PageNotFoundException::forPageNotFound(); } - $this->podcast = $podcast; + unset($params[0]); + unset($params[1]); - if (count($params) > 1) { - if ( - ! ($episode = (new EpisodeModel()) - ->where([ - 'id' => $params[1], - 'podcast_id' => $params[0], - ]) - ->first()) - ) { - throw PageNotFoundException::forPageNotFound(); - } - - $this->episode = $episode; - - unset($params[1]); - unset($params[0]); - } - - return $this->{$method}(...$params); + return $this->{$method}($episode, ...$params); } - public function list(): string + public function list(Episode $episode): string { - $soundbitesBuilder = (new ClipModel('audio')) + $soundbitesBuilder = new ClipModel('audio') ->where([ - 'podcast_id' => $this->podcast->id, - 'episode_id' => $this->episode->id, - 'type' => 'audio', + 'podcast_id' => $episode->podcast_id, + 'episode_id' => $episode->id, + 'type' => 'audio', ]) ->orderBy('created_at', 'desc'); $soundbites = $soundbitesBuilder->paginate(10); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, 'soundbites' => $soundbites, - 'pager' => $soundbitesBuilder->pager, + 'pager' => $soundbitesBuilder->pager, ]; + $this->setHtmlHead(lang('Soundbite.list.title')); replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/soundbites_list', $data); } - public function create(): string + public function createView(Episode $episode): string { helper(['form']); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, ]; + $this->setHtmlHead(lang('Soundbite.form.title')); replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/soundbites_new', $data); } - public function attemptCreate(): RedirectResponse + public function createAction(Episode $episode): RedirectResponse { $rules = [ - 'title' => 'required', + 'title' => 'required', 'start_time' => 'required|greater_than_equal_to[0]', - 'duration' => 'required|greater_than[0]', + 'duration' => 'required|greater_than[0]', ]; if (! $this->validate($rules)) { @@ -114,14 +107,16 @@ class SoundbiteController extends BaseController ->with('errors', $this->validator->getErrors()); } + $validData = $this->validator->getValidated(); + $newSoundbite = new Soundbite([ - 'title' => $this->request->getPost('title'), - 'start_time' => (float) $this->request->getPost('start_time'), - 'duration' => (float) $this->request->getPost('duration',), - 'type' => 'audio', - 'status' => '', - 'podcast_id' => $this->podcast->id, - 'episode_id' => $this->episode->id, + 'title' => $validData['title'], + 'start_time' => (float) $validData['start_time'], + 'duration' => (float) $validData['duration'], + 'type' => 'audio', + 'status' => '', + 'podcast_id' => $episode->podcast_id, + 'episode_id' => $episode->id, 'created_by' => user_id(), 'updated_by' => user_id(), ]); @@ -134,15 +129,16 @@ class SoundbiteController extends BaseController ->with('errors', $clipModel->errors()); } - return redirect()->route('soundbites-list', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('soundbites-list', [$episode->podcast_id, $episode->id])->with( 'message', - lang('Soundbite.messages.createSuccess') + lang('Soundbite.messages.createSuccess'), ); } - public function delete(string $soundbiteId): RedirectResponse + public function deleteAction(Episode $episode, string $soundbiteId): RedirectResponse { - $soundbite = (new ClipModel())->getSoundbiteById((int) $soundbiteId); + $soundbite = new ClipModel() + ->getSoundbiteById((int) $soundbiteId); if (! $soundbite instanceof Soundbite) { throw PageNotFoundException::forPageNotFound(); @@ -150,9 +146,11 @@ class SoundbiteController extends BaseController if ($soundbite->media === null) { // delete Clip directly - (new ClipModel())->deleteSoundbite($this->podcast->id, $this->episode->id, $soundbite->id); + new ClipModel() + ->deleteSoundbite($episode->podcast_id, $episode->id, $soundbite->id); } else { - (new ClipModel())->clearSoundbiteCache($this->podcast->id, $this->episode->id, $soundbite->id); + new ClipModel() + ->clearSoundbiteCache($episode->podcast_id, $episode->id, $soundbite->id); $mediaModel = new MediaModel(); // delete the soundbite file, the clip will be deleted on cascade @@ -164,9 +162,9 @@ class SoundbiteController extends BaseController } } - return redirect()->route('soundbites-list', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('soundbites-list', [$episode->podcast_id, $episode->id])->with( 'message', - lang('Soundbite.messages.deleteSuccess') + lang('Soundbite.messages.deleteSuccess'), ); } } diff --git a/modules/Admin/Controllers/UserController.php b/modules/Admin/Controllers/UserController.php deleted file mode 100644 index 3dc70d44..00000000 --- a/modules/Admin/Controllers/UserController.php +++ /dev/null @@ -1,258 +0,0 @@ -{$method}(); - } - - if ($this->user = (new UserModel())->find($params[0])) { - return $this->{$method}(); - } - - throw PageNotFoundException::forPageNotFound(); - } - - public function list(): string - { - $data = [ - 'users' => (new UserModel())->findAll(), - ]; - - return view('user/list', $data); - } - - public function view(): string - { - $data = [ - 'user' => $this->user, - ]; - - replace_breadcrumb_params([ - 0 => $this->user->username, - ]); - return view('user/view', $data); - } - - public function create(): string - { - helper('form'); - - $data = [ - 'roles' => (new GroupModel())->getUserRoles(), - ]; - - return view('user/create', $data); - } - - public function attemptCreate(): RedirectResponse - { - $userModel = new UserModel(); - - // Validate here first, since some things, - // like the password, can only be validated properly here. - $rules = array_merge( - $userModel->getValidationRules([ - 'only' => ['username'], - ]), - [ - 'email' => 'required|valid_email|is_unique[users.email]', - 'password' => 'required|strong_password', - ], - ); - - if (! $this->validate($rules)) { - return redirect() - ->back() - ->withInput() - ->with('errors', $this->validator->getErrors()); - } - - // Save the user - $user = new User($this->request->getPost()); - - // Activate user - $user->activate(); - - // Force user to reset his password on first connection - $user->forcePasswordReset(); - - if (! $userModel->insert($user)) { - return redirect() - ->back() - ->withInput() - ->with('errors', $userModel->errors()); - } - - // Success! - return redirect() - ->route('user-list') - ->with('message', lang('User.messages.createSuccess', [ - 'username' => $user->username, - ])); - } - - public function edit(): string - { - helper('form'); - - $roles = (new GroupModel())->getUserRoles(); - $roleOptions = array_reduce( - $roles, - static function ($result, $role) { - $result[$role->name] = lang('User.roles.' . $role->name); - return $result; - }, - [], - ); - - $data = [ - 'user' => $this->user, - 'roleOptions' => $roleOptions, - ]; - - replace_breadcrumb_params([ - 0 => $this->user->username, - ]); - return view('user/edit', $data); - } - - public function attemptEdit(): RedirectResponse - { - $authorize = Services::authorization(); - - $roles = $this->request->getPost('roles'); - - if ($this->user->isOwner) { - return redirect() - ->back() - ->with('errors', [ - lang('User.messages.editOwnerError', [ - 'username' => $this->user->username, - ]), - ]); - } - - $authorize->setUserGroups($this->user->id, $roles ?? []); - - // Success! - return redirect() - ->route('user-list') - ->with('message', lang('User.messages.rolesEditSuccess', [ - 'username' => $this->user->username, - ])); - } - - public function forcePassReset(): RedirectResponse - { - $userModel = new UserModel(); - $this->user->forcePasswordReset(); - - if (! $userModel->update($this->user->id, $this->user)) { - return redirect() - ->back() - ->with('errors', $userModel->errors()); - } - - // Success! - return redirect() - ->route('user-list') - ->with( - 'message', - lang('User.messages.forcePassResetSuccess', [ - 'username' => $this->user->username, - ]), - ); - } - - public function ban(): RedirectResponse - { - $authorize = Services::authorization(); - if ($authorize->inGroup('superadmin', $this->user->id)) { - return redirect() - ->back() - ->with('errors', [ - lang('User.messages.banSuperAdminError', [ - 'username' => $this->user->username, - ]), - ]); - } - - $userModel = new UserModel(); - // TODO: add ban reason? - $this->user->ban(''); - - if (! $userModel->update($this->user->id, $this->user)) { - return redirect() - ->back() - ->with('errors', $userModel->errors()); - } - - return redirect() - ->route('user-list') - ->with('message', lang('User.messages.banSuccess', [ - 'username' => $this->user->username, - ])); - } - - public function unBan(): RedirectResponse - { - $userModel = new UserModel(); - $this->user->unBan(); - - if (! $userModel->update($this->user->id, $this->user)) { - return redirect() - ->back() - ->with('errors', $userModel->errors()); - } - - return redirect() - ->route('user-list') - ->with('message', lang('User.messages.unbanSuccess', [ - 'username' => $this->user->username, - ])); - } - - public function delete(): RedirectResponse - { - $authorize = Services::authorization(); - if ($authorize->inGroup('superadmin', $this->user->id)) { - return redirect() - ->back() - ->with('errors', [ - lang('User.messages.deleteSuperAdminError', [ - 'username' => $this->user->username, - ]), - ]); - } - - (new UserModel())->delete($this->user->id); - - return redirect() - ->back() - ->with('message', lang('User.messages.deleteSuccess', [ - 'username' => $this->user->username, - ])); - } -} diff --git a/modules/Admin/Controllers/VideoClipsController.php b/modules/Admin/Controllers/VideoClipsController.php index 49878800..b91305ca 100644 --- a/modules/Admin/Controllers/VideoClipsController.php +++ b/modules/Admin/Controllers/VideoClipsController.php @@ -10,63 +10,57 @@ declare(strict_types=1); namespace Modules\Admin\Controllers; +use App\Entities\Clip\BaseClip; use App\Entities\Clip\VideoClip; use App\Entities\Episode; use App\Entities\Podcast; use App\Models\ClipModel; use App\Models\EpisodeModel; -use App\Models\MediaModel; use App\Models\PodcastModel; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; +use Modules\Media\Entities\Transcript; +use Modules\Media\Models\MediaModel; class VideoClipsController extends BaseController { - protected Podcast $podcast; - - protected Episode $episode; - public function _remap(string $method, string ...$params): mixed { + if ($params === []) { + throw PageNotFoundException::forPageNotFound(); + } + + if (count($params) === 1) { + if (! ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast) { + throw PageNotFoundException::forPageNotFound(); + } + + return $this->{$method}($podcast); + } + if ( - ($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) === null + ! ($episode = new EpisodeModel()->getEpisodeById((int) $params[1])) instanceof Episode ) { throw PageNotFoundException::forPageNotFound(); } - $this->podcast = $podcast; + unset($params[0]); + unset($params[1]); - if (count($params) > 1) { - if ( - ! ($episode = (new EpisodeModel()) - ->where([ - 'id' => $params[1], - 'podcast_id' => $params[0], - ]) - ->first()) - ) { - throw PageNotFoundException::forPageNotFound(); - } - - $this->episode = $episode; - - unset($params[1]); - unset($params[0]); - } - - return $this->{$method}(...$params); + return $this->{$method}($episode, ...$params); } - public function list(): string + public function list(Episode $episode): string { - $videoClipsBuilder = (new ClipModel('video')) + $videoClipsBuilder = new ClipModel('video') ->where([ - 'podcast_id' => $this->podcast->id, - 'episode_id' => $this->episode->id, - 'type' => 'video', + 'podcast_id' => $episode->podcast_id, + 'episode_id' => $episode->id, + 'type' => 'video', ]) ->orderBy('created_at', 'desc'); + /** @var BaseClip[] $clips */ $clips = $videoClipsBuilder->paginate(10); $videoClips = []; @@ -75,61 +69,67 @@ class VideoClipsController extends BaseController } $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, 'videoClips' => $videoClips, - 'pager' => $videoClipsBuilder->pager, + 'pager' => $videoClipsBuilder->pager, ]; + $this->setHtmlHead(lang('VideoClip.list.title')); replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); return view('episode/video_clips_list', $data); } - public function view($videoClipId): string + public function view(Episode $episode, string $videoClipId): string { - $videoClip = (new ClipModel())->getVideoClipById((int) $videoClipId); + $videoClip = new ClipModel() + ->getVideoClipById((int) $videoClipId); $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, 'videoClip' => $videoClip, ]; + $this->setHtmlHead(lang('VideoClip.title', [ + 'videoClipLabel' => esc($videoClip->title), + ])); replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, 2 => $videoClip->title, ]); return view('episode/video_clip', $data); } - public function create(): string + public function createView(Episode $episode): string { $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, + 'podcast' => $episode->podcast, + 'episode' => $episode, ]; replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->episode->title, + 0 => $episode->podcast->at_handle, + 1 => $episode->title, ]); // First, check that requirements to create a video clip are met $ffmpeg = shell_exec('which ffmpeg'); $checks = [ - 'ffmpeg' => $ffmpeg !== null, - 'gd' => extension_loaded('gd'), - 'freetype' => extension_loaded('gd') && gd_info()['FreeType Support'], - 'transcript' => $this->episode->transcript !== null, + 'ffmpeg' => $ffmpeg !== null, + 'gd' => extension_loaded('gd'), + 'freetype' => extension_loaded('gd') && gd_info()['FreeType Support'], + 'transcript' => $episode->transcript instanceof Transcript, ]; - if (in_array(false, $checks, true)) { - $data['checks'] = $checks; + $this->setHtmlHead(lang('VideoClip.form.title')); + if (array_any($checks, fn (bool $value): bool => ! $value)) { + $data['checks'] = $checks; return view('episode/video_clips_requirements', $data); } @@ -137,14 +137,14 @@ class VideoClipsController extends BaseController return view('episode/video_clips_new', $data); } - public function attemptCreate(): RedirectResponse + public function createAction(Episode $episode): RedirectResponse { $rules = [ - 'title' => 'required', + 'title' => 'required', 'start_time' => 'required|greater_than_equal_to[0]', - 'duration' => 'required|greater_than[0]', - 'format' => 'required|in_list[' . implode(',', array_keys(config('MediaClipper')->formats)) . ']', - 'theme' => 'required|in_list[' . implode(',', array_keys(config('Colors')->themes)) . ']', + 'duration' => 'required|greater_than[0]', + 'format' => 'required|in_list[' . implode(',', array_keys(config('MediaClipper')->formats)) . ']', + 'theme' => 'required|in_list[' . implode(',', array_keys(config('Colors')->themes)) . ']', ]; if (! $this->validate($rules)) { @@ -154,30 +154,32 @@ class VideoClipsController extends BaseController ->with('errors', $this->validator->getErrors()); } - $themeName = $this->request->getPost('theme'); + $validData = $this->validator->getValidated(); + + $themeName = $validData['theme']; $themeColors = config('MediaClipper') ->themes[$themeName]; $theme = [ - 'name' => $themeName, + 'name' => $themeName, 'preview' => $themeColors['preview'], ]; $videoClip = new VideoClip([ - 'title' => $this->request->getPost('title'), - 'start_time' => (float) $this->request->getPost('start_time'), - 'duration' => (float) $this->request->getPost('duration',), - 'theme' => $theme, - 'format' => $this->request->getPost('format'), - 'type' => 'video', - 'status' => 'queued', - 'podcast_id' => $this->podcast->id, - 'episode_id' => $this->episode->id, + 'title' => $validData['title'], + 'start_time' => (float) $validData['start_time'], + 'duration' => (float) $validData['duration'], + 'theme' => $theme, + 'format' => $validData['format'], + 'type' => 'video', + 'status' => 'queued', + 'podcast_id' => $episode->podcast_id, + 'episode_id' => $episode->id, 'created_by' => user_id(), 'updated_by' => user_id(), ]); // Check if video clip exists before inserting a new line - if ((new ClipModel())->doesVideoClipExist($videoClip)) { + if (new ClipModel()->doesVideoClipExist($videoClip)) { // video clip already exists return redirect() ->back() @@ -185,34 +187,38 @@ class VideoClipsController extends BaseController ->with('error', lang('VideoClip.messages.alreadyExistingError')); } - (new ClipModel())->insert($videoClip); + new ClipModel() + ->insert($videoClip); - return redirect()->route('video-clips-list', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('video-clips-list', [$episode->podcast_id, $episode->id])->with( 'message', - lang('VideoClip.messages.addToQueueSuccess') + lang('VideoClip.messages.addToQueueSuccess'), ); } - public function retry(string $videoClipId): RedirectResponse + public function retryAction(Episode $episode, string $videoClipId): RedirectResponse { - $videoClip = (new ClipModel())->getVideoClipById((int) $videoClipId); + $videoClip = new ClipModel() + ->getVideoClipById((int) $videoClipId); if (! $videoClip instanceof VideoClip) { throw PageNotFoundException::forPageNotFound(); } - (new ClipModel())->update($videoClip->id, [ - 'status' => 'queued', - 'job_started_at' => null, - 'job_ended_at' => null, - ]); + new ClipModel() + ->update($videoClip->id, [ + 'status' => 'queued', + 'job_started_at' => null, + 'job_ended_at' => null, + ]); return redirect()->back(); } - public function delete(string $videoClipId): RedirectResponse + public function deleteAction(Episode $episode, string $videoClipId): RedirectResponse { - $videoClip = (new ClipModel())->getVideoClipById((int) $videoClipId); + $videoClip = new ClipModel() + ->getVideoClipById((int) $videoClipId); if (! $videoClip instanceof VideoClip) { throw PageNotFoundException::forPageNotFound(); @@ -220,9 +226,11 @@ class VideoClipsController extends BaseController if ($videoClip->media === null) { // delete Clip directly - (new ClipModel())->deleteVideoClip($this->podcast->id, $this->episode->id, $videoClip->id); + new ClipModel() + ->deleteVideoClip($videoClip->id); } else { - (new ClipModel())->clearVideoClipCache($videoClip->id); + new ClipModel() + ->clearVideoClipCache($videoClip->id); $mediaModel = new MediaModel(); // delete the videoClip file, the clip will be deleted on cascade diff --git a/modules/Admin/Language/.rsync-filter b/modules/Admin/Language/.rsync-filter index 2a742b26..b802a93d 100644 --- a/modules/Admin/Language/.rsync-filter +++ b/modules/Admin/Language/.rsync-filter @@ -2,9 +2,11 @@ + fr/*** + pl/*** + de/*** -+ pt-BR/*** -+ nn-NO/*** ++ pt-br/*** ++ nn-no/*** + es/*** -+ zh-Hans/*** ++ zh-hans/*** + ca/*** ++ br/*** ++ sr-latn/*** - ** diff --git a/modules/Admin/Language/ar/AboutCastopod.php b/modules/Admin/Language/ar/AboutCastopod.php new file mode 100644 index 00000000..f5bc662b --- /dev/null +++ b/modules/Admin/Language/ar/AboutCastopod.php @@ -0,0 +1,22 @@ + 'حول كاستوبود', + 'host_name' => 'إسم المضيف', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'نظام التشغيل', + 'languages' => 'اللغات', + 'update_database' => 'تحديث قاعدة البيانات', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/ar/Breadcrumb.php b/modules/Admin/Language/ar/Breadcrumb.php index 0156d943..251d9788 100644 --- a/modules/Admin/Language/ar/Breadcrumb.php +++ b/modules/Admin/Language/ar/Breadcrumb.php @@ -19,6 +19,7 @@ return [ 'pages' => 'صفحات', 'settings' => 'الإعدادات', 'theme' => 'الحلة', + 'about' => 'about', 'add' => 'إضافة', 'new' => 'جديد', 'edit' => 'تعديل', @@ -28,15 +29,19 @@ return [ 'publish-date-edit' => 'edit publication date', 'unpublish' => 'unpublish', 'delete' => 'احذف', + 'remove' => 'remove', 'fediverse' => 'الفديفرس', - 'block-lists' => 'قوائم حجب', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', 'users' => 'مستخدمون', 'my-account' => 'حسابي', 'change-password' => 'تغيير الكلمة السرية', - 'import' => 'feed import', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', 'platforms' => 'منصات', 'social' => 'شبكات التواصل الاجتماعي', 'funding' => 'funding', + 'monetization-other' => 'other monetization', 'analytics' => 'analytics', 'locations' => 'locations', 'webpages' => 'صفحات ويب', diff --git a/modules/Admin/Language/ar/Charts.php b/modules/Admin/Language/ar/Charts.php index 4f0e8556..525085f0 100644 --- a/modules/Admin/Language/ar/Charts.php +++ b/modules/Admin/Language/ar/Charts.php @@ -9,7 +9,7 @@ declare(strict_types=1); */ return [ - 'by_service_weekly' => 'Episode downloads by service (for the past week)', + 'by_service_weekly' => 'تنزيلات الحلقة حسب الخدمة (للأسبوع الماضي)', 'by_player_weekly' => 'Episode downloads by player (for the past week)', 'by_player_yearly' => 'Episode downloads by player (for the past year)', 'by_device_weekly' => 'Episode downloads by device (for the past week)', @@ -24,7 +24,7 @@ return [ 'episode_by_month' => 'Episode monthly downloads', 'episodes_by_day' => '5 latest episodes downloads (during their first 60 days)', - 'by_country_weekly' => 'Episode downloads by country (for the past week)', + 'by_country_weekly' => 'تنزيلات الحلقة حسب البلد (في الأسبوع الماضي)', 'by_country_yearly' => 'Episode downloads by country (for the past year)', 'by_domain_weekly' => 'Web pages visits by source (for the past week)', 'by_domain_yearly' => 'Web pages visits by source (for the past year)', @@ -35,6 +35,7 @@ return [ 'by_weekday' => 'By week day (for the past 60 days)', 'by_hour' => 'By time of day (for the past 60 days)', 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', - 'total_storage_by_month' => 'Monthly storage (in MB)', + 'total_storage_by_month' => 'التخزين الشهري (بالميغابايت)', 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', ]; diff --git a/modules/Admin/Language/ar/Common.php b/modules/Admin/Language/ar/Common.php index 9dff13c6..fc39a83b 100644 --- a/modules/Admin/Language/ar/Common.php +++ b/modules/Admin/Language/ar/Common.php @@ -40,6 +40,7 @@ return [ ], 'upload_file' => 'إرسال ملف', 'remote_url' => 'Remote URL', + 'save' => 'Save', ], 'play_episode_button' => [ 'play' => 'تشغيل', diff --git a/modules/Admin/Language/ar/Episode.php b/modules/Admin/Language/ar/Episode.php index 99eed5ba..1a1cdeeb 100644 --- a/modules/Admin/Language/ar/Episode.php +++ b/modules/Admin/Language/ar/Episode.php @@ -22,6 +22,7 @@ return [ 'all_podcast_episodes' => 'كافة حلقات البودكاست', 'back_to_podcast' => 'العودة إلى البودكاست', 'edit' => 'تعديل', + 'preview' => 'Preview', 'publish' => 'نشر', 'publish_edit' => 'تعديل المنشور', 'publish_date_edit' => 'Edit publication date', @@ -55,6 +56,7 @@ return [ }', 'episode' => 'الحلقة', 'visibility' => 'الظهور', + 'downloads' => 'Downloads', 'comments' => 'التعليقات', 'actions' => 'الإجراءات', ], @@ -85,7 +87,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ @@ -137,9 +139,9 @@ return [ 'location_name' => 'Location name or address', 'location_name_hint' => 'This can be a real or fictional location', 'transcript' => 'Transcript (subtitles / closed captions)', - 'transcript_hint' => 'Only .srt are allowed.', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', 'transcript_download' => 'Download transcript', - 'transcript_file' => 'Transcript file (.srt)', + 'transcript_file' => 'Transcript file (.srt or .vtt)', 'transcript_remote_url' => 'Remote url for transcript', 'transcript_file_delete' => 'Delete transcript file', 'chapters' => 'الفصول', @@ -210,4 +212,14 @@ return [ 'light' => 'Light', 'light-transparent' => 'Light transparent', ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], ]; diff --git a/modules/Admin/Language/ar/Navigation.php b/modules/Admin/Language/ar/Navigation.php index f264138a..6a8bd0fa 100644 --- a/modules/Admin/Language/ar/Navigation.php +++ b/modules/Admin/Language/ar/Navigation.php @@ -12,12 +12,14 @@ return [ 'toggle_sidebar' => 'Toggle sidebar', 'go_to_website' => 'الانتقال إلى موقع الويب', 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', 'dashboard' => 'لوحة التحكم', 'admin' => 'الرئيسية', 'podcasts' => 'البودكاستات', 'podcast-list' => 'كافة البودكاستات', 'podcast-create' => 'بودكاست جديد', - 'podcast-import' => 'استيراد بودكاست', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', 'persons' => 'أشخاص', 'person-list' => 'All persons', 'person-create' => 'New person', @@ -33,6 +35,7 @@ return [ 'settings' => 'الإعدادات', 'settings-general' => 'العامة', 'settings-theme' => 'الحلة', + 'admin-about' => 'About', 'account' => [ 'my-account' => 'حسابي', 'change-password' => 'تغيير الكلمة السرية', diff --git a/modules/Admin/Language/ar/Notifications.php b/modules/Admin/Language/ar/Notifications.php index 2b139d51..8e86a2b3 100644 --- a/modules/Admin/Language/ar/Notifications.php +++ b/modules/Admin/Language/ar/Notifications.php @@ -9,11 +9,11 @@ declare(strict_types=1); */ return [ - 'title' => 'Notifications', + 'title' => 'الإشعارات', 'reply' => '{actor_username} replied to your post', 'favourite' => '{actor_username} favourited your post', 'reblog' => '{actor_username} shared your post', 'follow' => '{actor_username} started following you', - 'no_notifications' => 'No notifications', + 'no_notifications' => 'لا توجد إشعارات', 'mark_all_as_read' => 'Mark all as read', ]; diff --git a/modules/Admin/Language/ar/Page.php b/modules/Admin/Language/ar/Page.php index d4145ddc..31dde5a1 100644 --- a/modules/Admin/Language/ar/Page.php +++ b/modules/Admin/Language/ar/Page.php @@ -24,7 +24,7 @@ return [ 'submit_edit' => 'حفظ', ], 'messages' => [ - 'createSuccess' => 'The page “{pageTitle}” was created successfully!', + 'createSuccess' => 'تم إنشاء الصفحة "{pageTitle}" بنجاح!', 'editSuccess' => 'تم تحديث الصفحة بنجاح!', ], ]; diff --git a/modules/Admin/Language/ar/Platforms.php b/modules/Admin/Language/ar/Platforms.php index 5b17c098..7c01243d 100644 --- a/modules/Admin/Language/ar/Platforms.php +++ b/modules/Admin/Language/ar/Platforms.php @@ -9,9 +9,22 @@ declare(strict_types=1); */ return [ - 'title' => 'المنصات', + 'title' => [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', 'visible' => 'Display in podcast homepage?', 'on_embed' => 'Display on embeddable player?', 'remove' => 'Remove {platformName}', diff --git a/modules/Admin/Language/ar/Podcast.php b/modules/Admin/Language/ar/Podcast.php index bcd6f9dd..08ebcdd4 100644 --- a/modules/Admin/Language/ar/Podcast.php +++ b/modules/Admin/Language/ar/Podcast.php @@ -13,6 +13,7 @@ return [ 'no_podcast' => 'No podcast found!', 'create' => 'إنشاء بودكاست', 'import' => 'استيراد بودكاست', + 'all_imports' => 'Podcast imports', 'new_episode' => 'حلقة جديدة', 'view' => 'View podcast', 'edit' => 'Edit podcast', @@ -21,6 +22,7 @@ return [ 'delete' => 'Delete podcast', 'see_episodes' => 'See episodes', 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', 'go_to_page' => 'الانتقال إلى الصفحة', 'latest_episodes' => 'أحدث الحلقات', 'see_all_episodes' => 'See all episodes', @@ -48,7 +50,6 @@ return [ other {# episodes were} } added to the podcast!', 'podcastFeedUpToDate' => 'Podcast is already up to date.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', 'publishError' => 'This podcast is either already published or scheduled for publication.', 'publishEditError' => 'This podcast is not scheduled for publication.', 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', @@ -57,6 +58,8 @@ return [ 'form' => [ 'identity_section_title' => 'Podcast identity', 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + 'cover' => 'Podcast cover', 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', 'banner' => 'Podcast banner', @@ -71,7 +74,17 @@ return [ 'episodic' => 'Episodic', 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', 'serial' => 'Serial', - 'serial_hint' => 'If episodes are intended to be consumed in sequential order. The oldest episodes will be presented first.', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', ], 'description' => 'الوصف', 'classification_section_title' => 'Classification', @@ -96,6 +109,8 @@ return [ 'owner_email' => 'Owner email', 'owner_email_hint' => 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', 'publisher' => 'Publisher', 'publisher_hint' => 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', @@ -110,6 +125,11 @@ return [ 'premium' => 'Premium', 'premium_by_default' => 'Episodes must be set as premium by default', 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', 'payment_pointer' => 'Payment Pointer for Web Monetization', 'payment_pointer_hint' => 'This is your where you will receive money thanks to Web Monetization', @@ -118,11 +138,12 @@ return [ 'If you need RSS tags that Castopod does not handle, set them here.', 'custom_rss' => 'Custom RSS tags for the podcast', 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', 'new_feed_url' => 'New feed URL', 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', 'partnership' => 'Partnership', 'partner_id' => 'ID', 'partner_link_url' => 'Link URL', @@ -130,7 +151,6 @@ return [ 'partner_id_hint' => 'Your own partner ID', 'partner_link_url_hint' => 'The generic partner link address', 'partner_image_url_hint' => 'The generic partner image address', - 'status_section_title' => 'Status', 'block' => 'Podcast should be hidden from public catalogues', 'block_hint' => 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', diff --git a/modules/Admin/Language/ar/PodcastImport.php b/modules/Admin/Language/ar/PodcastImport.php deleted file mode 100644 index cccbaa65..00000000 --- a/modules/Admin/Language/ar/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'The podcast to import', - 'old_podcast_section_subtitle' => - 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', - 'imported_feed_url' => 'Feed URL', - 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', - 'new_podcast_section_title' => 'The new podcast', - 'advanced_params_section_title' => 'Advanced parameters', - 'advanced_params_section_subtitle' => - 'Keep the default values if you have no idea of what the fields are for.', - 'slug_field' => 'Field to be used to calculate episode slug', - 'description_field' => - 'Source field used for episode description / show notes', - 'force_renumber' => 'Force episodes renumbering', - 'force_renumber_hint' => - 'Use this if your podcast does not have episode numbers but wish to set them during import.', - 'season_number' => 'رقم الموسم', - 'season_number_hint' => - 'Use this if your podcast does not have a season number but wish to set one during import. Leave blank otherwise.', - 'max_episodes' => 'Maximum number of episodes to import', - 'max_episodes_hint' => 'Leave blank to import all episodes', - 'lock_import' => - 'This feed is protected. You cannot import it. If you are the owner, unprotect it on the origin platform.', - 'submit' => 'Import podcast', -]; diff --git a/modules/Admin/Language/ar/PodcastNavigation.php b/modules/Admin/Language/ar/PodcastNavigation.php index ad114d10..b82f78d9 100644 --- a/modules/Admin/Language/ar/PodcastNavigation.php +++ b/modules/Admin/Language/ar/PodcastNavigation.php @@ -10,10 +10,13 @@ declare(strict_types=1); return [ 'go_to_page' => 'انتقل إلى صفحة البودكاست', + 'rss_feed' => 'RSS feed', 'dashboard' => 'Podcast dashboard', 'podcast-view' => 'الرئيسية', 'podcast-edit' => 'Edit podcast', 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', 'episodes' => 'الحلقات', 'episode-list' => 'جميع الحلقات', 'episode-create' => 'حلقة جديدة', @@ -25,14 +28,15 @@ return [ 'podcast-analytics-players' => 'Players', 'podcast-analytics-listening-time' => 'Listening time', 'podcast-analytics-time-periods' => 'Time periods', - 'premium' => 'Premium', + 'monetization' => 'Monetization', 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', + 'subscription-create' => 'Add subscription', 'contributors' => 'المساهمون', 'contributor-list' => 'كل المساهمين', 'contributor-add' => 'إضافة مساهم', - 'platforms' => 'External platforms', - 'platforms-podcasting' => 'Podcasting', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', 'platforms-social' => 'Social networks', - 'platforms-funding' => 'Funding', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', ]; diff --git a/modules/Admin/Language/ar/Validation.php b/modules/Admin/Language/ar/Validation.php index 750b1968..f76c3163 100644 --- a/modules/Admin/Language/ar/Validation.php +++ b/modules/Admin/Language/ar/Validation.php @@ -13,6 +13,5 @@ return [ '{field} is either not an image, or it is not wide or tall enough.', 'is_image_ratio' => '{field} is either not an image or not of the right ratio.', - 'validate_url' => - 'The {field} field must be a valid URL (eg. https://example.com/).', + 'is_json' => '{field} contains invalid JSON.', ]; diff --git a/modules/Admin/Language/br/AboutCastopod.php b/modules/Admin/Language/br/AboutCastopod.php new file mode 100644 index 00000000..d0f56d6b --- /dev/null +++ b/modules/Admin/Language/br/AboutCastopod.php @@ -0,0 +1,22 @@ + 'Diwar-benn Castopod', + 'host_name' => 'Anv an ostiz', + 'version' => 'Stumm Castopod', + 'php_version' => 'Stumm PHP', + 'os' => 'Sistem oberiañ', + 'languages' => 'Yezhoù', + 'update_database' => 'Nevesaat an diaz roadennoù', + 'messages' => [ + 'databaseUpdateSuccess' => 'Nevesaet eo an diaz roadennoù!', + ], +]; diff --git a/modules/Admin/Language/br/Breadcrumb.php b/modules/Admin/Language/br/Breadcrumb.php index c2eb298a..65d62836 100644 --- a/modules/Admin/Language/br/Breadcrumb.php +++ b/modules/Admin/Language/br/Breadcrumb.php @@ -14,29 +14,34 @@ return [ ->gateway => 'Degemer', 'podcasts' => 'podkastoù', 'episodes' => 'rannoù', - 'subscriptions' => 'subscriptions', + 'subscriptions' => 'koumanantoù', 'contributors' => 'perzhidi, perzhiadezed', 'pages' => 'pajennoù', 'settings' => 'arventennoù', 'theme' => 'neuz', + 'about' => 'a-zivout', 'add' => 'ouzhpennañ', 'new' => 'krouiñ', 'edit' => 'kemmañ', 'persons' => 'emellerien·ezed', 'publish' => 'embann', 'publish-edit' => 'kemmañ an embannadur', - 'publish-date-edit' => 'edit publication date', + 'publish-date-edit' => 'kemmañ deiziad an embannadur', 'unpublish' => 'diembannañ', 'delete' => 'dilemel', - 'fediverse' => 'kevrebed', - 'block-lists' => 'roll ar re stanket', + 'remove' => 'lemel', + 'fediverse' => 'fediverse', + 'blocked-actors' => 'aktourien·ezed stanket', + 'blocked-domains' => 'domanioù stanket', 'users' => 'implijerien·ezed', 'my-account' => 'ma c\'hont', 'change-password' => 'kemmañ ar ger-tremen', - 'import' => 'enporzhiañ ul lanv', + 'imports' => 'enporzhiadennoù', + 'sync-feeds' => 'sinkronekaat ar gwazhoù', 'platforms' => 'savennoù', 'social' => 'rouedadoù sokial', 'funding' => 'arc\'hantaouiñ', + 'monetization-other' => 'doare arc\'hantaouiñ all', 'analytics' => 'muzulioù heklev', 'locations' => 'lec\'hioù', 'webpages' => 'pajennoù web', @@ -45,8 +50,8 @@ return [ 'listening-time' => 'padelezh ar selaou', 'time-periods' => 'mareoù ar selaou', 'soundbites' => 'tennadoù son', - 'video-clips' => 'tennadoù video', + 'video-clips' => 'klipoù video', 'embed' => 'lenner enkorfet', - 'notifications' => 'notifications', - 'suspend' => 'suspend', + 'notifications' => 'kemennoù', + 'suspend' => 'ehanañ', ]; diff --git a/modules/Admin/Language/br/Charts.php b/modules/Admin/Language/br/Charts.php index 7de3c6ac..687ea057 100644 --- a/modules/Admin/Language/br/Charts.php +++ b/modules/Admin/Language/br/Charts.php @@ -37,4 +37,5 @@ return [ 'podcast_by_bandwidth' => 'Lec\'hed bann implijet bemdez (e MB)', 'total_storage_by_month' => 'Kadaviñ bep miz (e MB)', 'total_bandwidth_by_month' => 'Lec\'hed bann implijet bep miz (e MB)', + 'total_bandwidth_by_month_limit' => 'Bevennet da {totalBandwidth} bep miz', ]; diff --git a/modules/Admin/Language/br/Common.php b/modules/Admin/Language/br/Common.php index 1cb6c3b2..40491496 100644 --- a/modules/Admin/Language/br/Common.php +++ b/modules/Admin/Language/br/Common.php @@ -14,7 +14,7 @@ return [ 'cancel' => 'Nullañ', 'optional' => 'Diret', 'more' => 'Muioc\'h', - 'no_data' => 'Roadenn ebet kavet!', + 'no_data' => 'Roadenn ebet kavet !', 'close' => 'Serriñ', 'edit' => 'Kemmañ', 'copy' => 'Eilañ', @@ -40,6 +40,7 @@ return [ ], 'upload_file' => 'Uskargit ur restr', 'remote_url' => 'URL a-bell', + 'save' => 'Enrollañ', ], 'play_episode_button' => [ 'play' => 'Lenn', diff --git a/modules/Admin/Language/br/Countries.php b/modules/Admin/Language/br/Countries.php index 2187df89..229abb0d 100644 --- a/modules/Admin/Language/br/Countries.php +++ b/modules/Admin/Language/br/Countries.php @@ -116,7 +116,7 @@ return [ 'IL' => 'Israel', 'IM' => 'Enez-Vanav', 'IN' => 'Indez', - 'IO' => 'British Indian Ocean Territory', + 'IO' => 'Tiriad Meurvor Indez Breizh-Veur', 'IQ' => 'Irak', 'IR' => 'Iran', 'IS' => 'Island', @@ -136,7 +136,7 @@ return [ 'KW' => 'Kowait', 'KY' => 'Cayman, Inizi', 'KZ' => 'Kazakstan', - 'LA' => "Lao People's Democratic Republic", + 'LA' => "Bro-Laos", 'LB' => 'Liban', 'LC' => 'Santez-Lusia', 'LI' => 'Liechtenstein', @@ -210,7 +210,7 @@ return [ 'SD' => 'Soudan', 'SE' => 'Sveden', 'SG' => 'Singapoura', - 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', + 'SH' => 'Saint Helena, Ascension ha Tristan da Cunha', 'SI' => 'Sloveni', 'SJ' => 'Svalbard ha Jan Mayen', 'SK' => 'Slovaki', @@ -243,7 +243,7 @@ return [ 'TZ' => 'Tanzania, Republik Unanet', 'UA' => 'Ukraina', 'UG' => 'Ouganda', - 'UM' => 'United States Minor Outlying Islands', + 'UM' => 'Inizi bihan diabell ar Stadoù-Unanet', 'US' => 'Stadoù-Unanet', 'UY' => 'Uruguay', 'UZ' => 'Uzbekistan', diff --git a/modules/Admin/Language/br/Dashboard.php b/modules/Admin/Language/br/Dashboard.php index 881073fd..255e0aca 100644 --- a/modules/Admin/Language/br/Dashboard.php +++ b/modules/Admin/Language/br/Dashboard.php @@ -9,20 +9,20 @@ declare(strict_types=1); */ return [ - 'home' => 'Admin dashboard', - 'welcome_message' => 'Welcome to the admin area!', + 'home' => 'Taolenn-stur', + 'welcome_message' => 'Degemer mat en daolenn-stur!', 'podcasts' => [ - 'title' => 'Podcasts', - 'not_found' => 'No published podcast', - 'last_published' => 'Last published on {lastPublicationDate}', + 'title' => 'Podkastoù', + 'not_found' => 'Podkast embannet ebet', + 'last_published' => 'Embannet da ziwezhañ d\'an/ar {lastPublicationDate}', ], 'episodes' => [ - 'title' => 'Episodes', - 'not_found' => 'No published episode', - 'last_published' => 'Last published on {lastPublicationDate}', + 'title' => 'Rannoù', + 'not_found' => 'Rann embannet ebet', + 'last_published' => 'Embannet da ziwezhañ d\'an/ar {lastPublicationDate}', ], 'storage' => [ - 'title' => 'Storage', - 'subtitle' => '{totalUploaded} out of {totalStorage}', + 'title' => 'Stokañ', + 'subtitle' => '{totalUploaded} war {totalStorage}', ], ]; diff --git a/modules/Admin/Language/br/Episode.php b/modules/Admin/Language/br/Episode.php index 56e1c93d..7f73831b 100644 --- a/modules/Admin/Language/br/Episode.php +++ b/modules/Admin/Language/br/Episode.php @@ -23,16 +23,17 @@ return [ 'all_podcast_episodes' => 'Holl rannoù ar podkast', 'back_to_podcast' => 'Mont d\'ar podkast en-dro', 'edit' => 'Kemmañ', + 'preview' => 'Rakwel', 'publish' => 'Embann', 'publish_edit' => 'Kemmañ an embannadur', - 'publish_date_edit' => 'Edit publication date', + 'publish_date_edit' => 'Kemmañ deiziad an embannadur', 'unpublish' => 'Diembannañ', 'publish_error' => 'Embannet eo bet ar rann dija.', 'publish_edit_error' => 'Embannet eo bet ar rann dija.', 'publish_cancel_error' => 'Embannet eo bet ar rann dija.', - 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', - 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', - 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'publish_date_edit_error' => 'N\'eo ket bet embannet ar podkast c\'hoazh, ne c\'hallit ket kemmañ an deiziad an embann.', + 'publish_date_edit_future_error' => 'Rankout a ra deiziad embannadur ar rann bezañ en amzer dremenet! M\'ho peus c\'hoant da steuñviñ an embannadur en amzer da zont e rankit diembann ar rann da gentañ.', + 'publish_date_edit_success' => 'Cheñchet eo bet deiziad embannadur ar rann gant berzh!', 'unpublish_error' => 'N\'eo ket bet embannet ar rann.', 'delete' => 'Dilemel', 'go_to_page' => 'Gwelet ar bajenn', @@ -57,15 +58,16 @@ return [ }', 'episode' => 'Rann', 'visibility' => 'Gwelusted', + 'downloads' => 'Pellgargadennoù', 'comments' => 'Evezhiadennoù', 'actions' => 'Obererezhioù', ], 'messages' => [ 'createSuccess' => 'Krouet eo bet ar rann gant berzh!', - 'editSuccess' => 'Hizivaet eo bet ar rann gant berzh!', + 'editSuccess' => 'Nevesaet eo bet ar rann gant berzh!', 'publishSuccess' => '{publication_status, select, - published {Embannet eo bet ar rann gant berzh!} - scheduled {Raktreset eo bet embannadur ar rann gant berzh!} + published {Embannet eo bet ar rann gant berzh !} + scheduled {Raktreset eo bet embannadur ar rann gant berzh !} with_podcast {Ar rann-mañ a vo embannet war un dro gant ar podkast.} other {N\'eo ket bet embannet ar rann-mañ.} }', @@ -87,7 +89,7 @@ return [ image {ar golo} audio {an aodio} other {ar media} - } ({file_path}). Gallout a rit lemel kuit ar restr-mañ diouzh ar gantenn dre zorn.', + } {file_key}. Gallout a rit lemel kuit ar restr-mañ diouzh ar gantenn dre zorn.', 'sameSlugError' => 'Bez ez eus eus ur rann gant ar berradur-mañ (slug) dija.', ], 'form' => [ @@ -116,7 +118,7 @@ return [ 'bonus_hint' => 'Danvez ouzhpenn ar podkast (da skouer, titouroù diwar-benn kostezioù pe atersadennoù gant an aktourien·ezed), pe bruderezh kroaziet evit ur podkast all', ], 'premium_title' => 'Premium', - 'premium' => 'Episode must be accessible to premium subscribers only', + 'premium' => 'Ne vo gwelet ar rann nemet gant koumananterien·ezed Premium', 'parental_advisory' => [ 'label' => 'Kemenn evit ar gerent', 'hint' => 'Hag ar rann-mañ a zo danvez ha ne zlefe ket gwelet gant bugale?', @@ -139,9 +141,9 @@ return [ 'location_name' => 'Anv pe chomlec\'h al lec\'h', 'location_name_hint' => 'Al lec\'h-mañ a c\'hell bezañ unan gwir pe unan faltaziet', 'transcript' => 'Treuzskrivadur (istitloù)', - 'transcript_hint' => 'Aotreet e vez nemet .srt.', + 'transcript_hint' => 'Aotreet e vez nemet .srt pe .vtt.', 'transcript_download' => 'Pellgargañ an treuzskrivadur', - 'transcript_file' => 'Restr an treuzskrivadur (.srt)', + 'transcript_file' => 'Restr an treuzskrivadur (.srt pe .vtt)', 'transcript_remote_url' => 'URL a-bell evit restr an treuzskrivadur', 'transcript_file_delete' => 'Dilemel restr an treuzskrivadur', 'chapters' => 'Chabistroù', @@ -165,7 +167,7 @@ return [ 'back_to_episode_dashboard' => 'Distreiñ da daolenn-stur ar rann', 'post' => 'Ho kemennadenn vrudañ', 'post_hint' => - "Skrivit ur gemennadenn evit brudañ embannadur ho rann. Skignet e vo ar gemennadenn-se d'an holl re a heuilh ac'hanoc'h war ar c'hevrebed (fediverse) ha lakaet e vo war well war pajenn ho podkast.", + "Skrivit ur gemennadenn evit brudañ embannadur ho rann. Skignet e vo ar gemennadenn-se d'an holl re a heuilh ac'hanoc'h war ar Fediverse ha lakaet e vo war well war pajenn ho podkast.", 'message_placeholder' => 'Skrivit ho kemennadenn…', 'publication_date' => 'Deiziad embannadur', 'publication_method' => [ @@ -178,26 +180,26 @@ return [ 'scheduled_publication_date_hint' => 'Gallout a rit steuñviñ embannadur ar rann en ur steuñviñ embannadur ar rann en dazont. Dleout a ra ar vaezienn bezañ er furmad YYYY-MM-DD HH:mm', 'submit' => 'Embann', - 'submit_edit' => 'Kemmañ an embannadur', + 'submit_edit' => 'Kemmañ an embann', 'cancel_publication' => 'Nullañ an embannadur', 'message_warning' => 'N\'ho peus ket skrivet ur gemennadenn evit brudañ ho rann!', 'message_warning_hint' => 'Ouzhpennañ ur gemennadenn a lakay muioc\'h a dud er jeu, ha diwar se e vo gwelet muioc\'h ho rann.', 'message_warning_submit' => 'Embann memestra', ], 'publish_date_edit_form' => [ - 'new_publication_date' => 'New publication date', - 'new_publication_date_hint' => 'Must be set to a past date.', - 'submit' => 'Edit publication date', + 'new_publication_date' => 'Deiziad embannadur nevez', + 'new_publication_date_hint' => 'An dra-se a rank bezañ un deiziad tremenet.', + 'submit' => 'Kemmañ deiziad an embann', ], 'unpublish_form' => [ 'disclaimer' => - "Diembann ar rann a zilamo an holl gemennadennoù liammet outi ha skarzhet e vo eus lanv RSS ar podkast.", + "Diembann ar rann a zilamo an holl gemennadennoù liammet outi ha skarzhet e vo eus gwazh RSS ar podkast.", 'understand' => 'Komprennet eo, diembann ar rann a fell din', 'submit' => 'Diembann', ], 'delete_form' => [ 'disclaimer' => - "Gant ar rann e vo dilamet an holl restroù media, evezhiadennoù, tennadoù video ha son liammet outi.", + "Gant ar rann e vo dilamet an holl restroù media, evezhiadennoù, klipoù video ha son liammet outi.", 'understand' => 'Komprennet eo, dilemel ar rann a fell din', 'submit' => 'Dilemel', ], @@ -212,4 +214,14 @@ return [ 'light' => 'Sklaer', 'light-transparent' => 'Sklaer treuzwelus', ], + 'publication_status_banner' => [ + 'draft_mode' => 'mod brouilhed', + 'text' => '{publication_status, select, + published {N\'eo ket bet embannet ar rann-mañ c\'hoazh.} + scheduled {Raktreset eo an embann a-benn an/ar {publication_date}.} + with_podcast {Ar rann-mañ a vo embannet war un dro gant ar podkast.} + other {N\'eo ket bet embannet ar rann-mañ c\'hoazh.} + }', + 'preview' => 'Rakwel', + ], ]; diff --git a/modules/Admin/Language/br/EpisodeNavigation.php b/modules/Admin/Language/br/EpisodeNavigation.php index 214e2aca..7f4c0afc 100644 --- a/modules/Admin/Language/br/EpisodeNavigation.php +++ b/modules/Admin/Language/br/EpisodeNavigation.php @@ -15,9 +15,9 @@ return [ 'episode-edit' => 'Kemm ar rann', 'episode-persons-manage' => 'Merañ an emellerien·ezed', 'embed-add' => 'Lenner enkorfet', - 'clips' => 'Tennadoù', - 'video-clips-list' => 'Tennadoù video', - 'video-clips-create' => 'Tennad video nevez', + 'clips' => 'Klipoù', + 'video-clips-list' => 'Klipoù video', + 'video-clips-create' => 'Klip video nevez', 'soundbites-list' => 'Tennadoù son', 'soundbites-create' => 'Tennad son nevez', ]; diff --git a/modules/Admin/Language/br/Fediverse.php b/modules/Admin/Language/br/Fediverse.php index 9dd1559a..f8aeebf4 100644 --- a/modules/Admin/Language/br/Fediverse.php +++ b/modules/Admin/Language/br/Fediverse.php @@ -10,7 +10,7 @@ declare(strict_types=1); return [ 'messages' => [ - 'actorNotFound' => 'N\'eo ket bet kavet ar gont-se!', + 'actorNotFound' => 'N\'eo ket bet kavet ar gont-se !', 'blockActorSuccess' => 'Stanket eo bet {actor}!', 'unblockActorSuccess' => 'Distanket eo bet an implijer·ez!', 'blockDomainSuccess' => 'Stanket eo bet {domain}!', diff --git a/modules/Admin/Language/br/Home.php b/modules/Admin/Language/br/Home.php index 285392b4..6cbea879 100644 --- a/modules/Admin/Language/br/Home.php +++ b/modules/Admin/Language/br/Home.php @@ -10,5 +10,5 @@ declare(strict_types=1); return [ 'all_podcasts' => 'An holl bodkastoù', - 'no_podcast' => 'N\'eo bet kavet podkast ebet', + 'no_podcast' => 'N\'eus bet kavet podkast ebet', ]; diff --git a/modules/Admin/Language/br/Install.php b/modules/Admin/Language/br/Install.php index e73f08a3..2f86bafd 100644 --- a/modules/Admin/Language/br/Install.php +++ b/modules/Admin/Language/br/Install.php @@ -11,7 +11,7 @@ declare(strict_types=1); return [ 'manual_config' => 'Kefluniañ dre zorn', 'manual_config_subtitle' => - 'Krouit ur restr `.env` gant hoc’h arventennoù ha hizivait ar bajenn evit kenderc\'hel gant ar staliañ.', + 'Krouit ur restr `.env` gant hoc’h arventennoù ha nevesait ar bajenn evit kenderc\'hel gant ar staliañ.', 'form' => [ 'instance_config' => 'Arventennoù an istañs', 'hostname' => 'Anv an ostiz', diff --git a/modules/Admin/Language/br/Navigation.php b/modules/Admin/Language/br/Navigation.php index b82240f9..de1da33e 100644 --- a/modules/Admin/Language/br/Navigation.php +++ b/modules/Admin/Language/br/Navigation.php @@ -12,16 +12,18 @@ return [ 'toggle_sidebar' => 'Kuzhat/diskouez ar varrenn gostez', 'go_to_website' => 'Mont d\'al lec\'hienn', 'go_to_admin' => 'Mont d\'an daolenn-stur', + 'not-authorized' => 'Difennet', 'dashboard' => 'Taolenn-stur', 'admin' => 'Degemer', 'podcasts' => 'Podkastoù', 'podcast-list' => 'An holl bodkastoù', 'podcast-create' => 'Krouiñ ur podkast', - 'podcast-import' => 'Enporzhiañ ur podkast', + 'all-podcast-imports' => 'An holl bodkastoù enporzhiet', + 'podcast-imports-add' => 'Enporzhiañ ur podkast', 'persons' => 'Emellerien·ezed', 'person-list' => 'An holl emellerien·ezed', 'person-create' => 'Krouiñ un emeller·ez', - 'fediverse' => 'Kevrebed', + 'fediverse' => 'Fediverse', 'fediverse-blocked-actors' => 'Implijerien·ezed stanket', 'fediverse-blocked-domains' => 'Domanioù stanket', 'users' => 'Implijerien·ezed', @@ -33,6 +35,7 @@ return [ 'settings' => 'Arventennoù', 'settings-general' => 'Hollek', 'settings-theme' => 'Neuz', + 'admin-about' => 'A-zivout', 'account' => [ 'my-account' => 'Ma c\'hont', 'change-password' => 'Kemmañ ar ger-tremen', diff --git a/modules/Admin/Language/br/Notifications.php b/modules/Admin/Language/br/Notifications.php index 2b139d51..3f2ed5f5 100644 --- a/modules/Admin/Language/br/Notifications.php +++ b/modules/Admin/Language/br/Notifications.php @@ -9,11 +9,11 @@ declare(strict_types=1); */ return [ - 'title' => 'Notifications', - 'reply' => '{actor_username} replied to your post', - 'favourite' => '{actor_username} favourited your post', - 'reblog' => '{actor_username} shared your post', - 'follow' => '{actor_username} started following you', - 'no_notifications' => 'No notifications', - 'mark_all_as_read' => 'Mark all as read', + 'title' => 'Kemennoù', + 'reply' => 'Respontet eo bet d\'ho kemennadenn gant {actor_username}', + 'favourite' => 'Ouzhpennet eo bet ho kemennadenn d\'h·e re garetañ gant {actor_username}', + 'reblog' => 'Rannet eo bet ho kemennadenn gant {actor_username}', + 'follow' => 'Krog eo {actor_username} da heuliañ ac\'hanoc\'h', + 'no_notifications' => 'Kemenn ebet', + 'mark_all_as_read' => 'Merkañ pep tra evel lennet', ]; diff --git a/modules/Admin/Language/br/Page.php b/modules/Admin/Language/br/Page.php index dccaa097..b99aa723 100644 --- a/modules/Admin/Language/br/Page.php +++ b/modules/Admin/Language/br/Page.php @@ -25,6 +25,6 @@ return [ ], 'messages' => [ 'createSuccess' => 'Krouet eo bet ar bajenn "{pageTitle}" gant berzh!', - 'editSuccess' => 'Hizivaet eo bet ar bajenn gant berzh!', + 'editSuccess' => 'Nevesaet eo bet ar bajenn gant berzh!', ], ]; diff --git a/modules/Admin/Language/br/Person.php b/modules/Admin/Language/br/Person.php index 9a1d0f69..e91d9fcd 100644 --- a/modules/Admin/Language/br/Person.php +++ b/modules/Admin/Language/br/Person.php @@ -11,14 +11,14 @@ declare(strict_types=1); return [ 'persons' => 'Emellerien·ezed', 'all_persons' => 'An holl emellerien·ezed', - 'no_person' => 'Emeller·ez ebet!', + 'no_person' => 'Emeller·ez ebet !', 'create' => 'Krouiñ un emeller·ez', 'view' => 'Gwelet an emeller·ez', 'edit' => 'Kemmañ an emeller·ez', 'delete' => 'Dilemel an emeller·ez', 'messages' => [ 'createSuccess' => 'Krouet eo bet an emeller·ez gant berzh!', - 'editSuccess' => 'Hizivaet eo bet an emeller·ez gant berzh!', + 'editSuccess' => 'Nevesaet eo bet an emeller·ez gant berzh!', 'deleteSuccess' => 'Tennet eo bet an emeller·ez!', ], 'form' => [ diff --git a/modules/Admin/Language/br/Platforms.php b/modules/Admin/Language/br/Platforms.php index 71d04168..959e9f60 100644 --- a/modules/Admin/Language/br/Platforms.php +++ b/modules/Admin/Language/br/Platforms.php @@ -9,15 +9,28 @@ declare(strict_types=1); */ return [ - 'title' => 'Savennoù', + 'title' => [ + 'podcasting' => 'Savennoù podkastoù', + 'social' => 'Rouedadoù sokial', + 'funding' => 'Liammoù arc\'hantaouiñ', + ], + 'website' => 'Lec\'hienn', 'home_url' => 'Mont da lec\'hienn {platformName}', + 'register' => 'Enskrivañ', 'submit_url' => 'Kasit ho podkast war {platformName}', + 'your_link' => 'Ho liamm', + 'your_id' => [ + 'podcasting' => 'Hoc’h anaout (ID)', + 'social' => 'Hoc’h anaout (ID)', + 'funding' => 'Ho CTA', + ], + 'your_cta' => 'Ho kalv da ober', 'visible' => 'Diskouez e pajenn ar podkast?', 'on_embed' => 'Diskouez el lenner enkorfet?', 'remove' => 'Dilemel {platformName}', 'submit' => 'Enrollañ', 'messages' => [ - 'updateSuccess' => 'Hizivaet eo bet ereoù ar savennoù gant berzh!', + 'updateSuccess' => 'Nevesaet eo bet ereoù ar savennoù gant berzh!', 'removeLinkSuccess' => 'Dilamet eo bet ere ar savenn.', 'removeLinkError' => 'N\'eo ket bet dilamet ere ar savenn. Klaskit en-dro.', diff --git a/modules/Admin/Language/br/Podcast.php b/modules/Admin/Language/br/Podcast.php index 0ecaa7fb..2a754f54 100644 --- a/modules/Admin/Language/br/Podcast.php +++ b/modules/Admin/Language/br/Podcast.php @@ -13,6 +13,7 @@ return [ 'no_podcast' => 'N\'eo bet kavet podkast ebet!', 'create' => 'Krouiñ ur podkast', 'import' => 'Enporzhiañ ur podkast', + 'all_imports' => 'Ar podkastoù enporzhiet', 'new_episode' => 'Rann nevez', 'view' => 'Gwelet ar podkast', 'edit' => 'Kemmañ ar podkast', @@ -21,14 +22,15 @@ return [ 'delete' => 'Dilemel ar podkast', 'see_episodes' => 'Gwelet ar rannoù', 'see_contributors' => 'Gwelet ar berzhidi, ar perzhiadezed', + 'monetization_other' => 'Doare arc\'hantaouiñ all', 'go_to_page' => 'Gwelet ar bajenn', 'latest_episodes' => 'Rannoù diwezhañ', 'see_all_episodes' => 'Gwelet an holl rannoù', 'draft' => 'Brouilhed', 'messages' => [ 'createSuccess' => 'Krouet eo bet ar podkast gant berzh!', - 'editSuccess' => 'Hizivaet eo bet ar podkast gant berzh!', - 'importSuccess' => 'Enporzhet eo bet ar podkast gant berzh!', + 'editSuccess' => 'Nevesaet eo bet ar podkast gant berzh!', + 'importSuccess' => 'Enporzhiet eo bet ar podkast gant berzh!', 'deleteSuccess' => 'Dilamet eo bet ar podkast @{podcast_handle} gant berzh!', 'deletePodcastMediaError' => 'C\'hwitadenn war dilemel {type, select, cover {golo} @@ -43,15 +45,14 @@ return [ other {media} } ar rann {episode_slug}.', 'deletePodcastMediaFolderError' => 'C\'hwitadenn war dilemel teuliad ar mediaioù {folder_path}. Gallout a rit lemel an teuliad-mañ diouzh ar gantenn dre zorn.', - 'podcastFeedUpdateSuccess' => 'Hizivadenn: {number_of_new_episodes, plural, + 'podcastFeedUpdateSuccess' => 'Nevesadenn: {number_of_new_episodes, plural, one {# rann} two {# rann} few {# rann} many {# rann} other {# rann} } a zo bet ouzhpennet d\'ar podkast gant berzh!', - 'podcastFeedUpToDate' => 'Hizivaet eo bet ar podkast dija.', - 'podcastNotImported' => 'C\'hwitadenn war hizivadenn ar podkast peogwir n\'eo ket bet enporzhiet.', + 'podcastFeedUpToDate' => 'Nevesaet eo bet ar podkast dija.', 'publishError' => 'Ar podkast-mañ a zo bet embannet dija pe steuñvet eo e embannadur.', 'publishEditError' => 'N\'eo ket steuñvet embannadur ar podkast-mañ.', 'publishCancelSuccess' => 'Nullet eo bet embannadur ar podkast gant berzh!', @@ -60,6 +61,8 @@ return [ 'form' => [ 'identity_section_title' => 'Titouroù diwar-benn ar podkast', 'identity_section_subtitle' => 'Ar maeziennoù a laka ac\'hanoc\'h da vezañ remerket.', + 'fediverse_section_title' => 'Identelezh er Fediverse', + 'cover' => 'Golo ar podkast', 'cover_size_hint' => 'Ar golo a rankfe bezañ ur c\'harrez ha 1400px e vent da nebeutañ.', 'banner' => 'Giton ar podkast', @@ -74,7 +77,17 @@ return [ 'episodic' => 'Bep ur mare', 'episodic_hint' => 'M\'eo ar rannoù da vezañ selaouet hep urzh resis. Ar rannoù nevesoc’h a vo kinniget da gentañ.', 'serial' => 'Heuliad', - 'serial_hint' => 'M\'eo ar rannoù da vezañ selaouet gant un urzh resis. Ar rannoù koshoc\'h a vo kinniget da gentañ.', + 'serial_hint' => 'M\'eo ar rannoù da vezañ selaouet gant un urzh resis. Ar rannoù a vo kinniget hervez urzh an niverennoù.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Ar medium evel ma vez kinniget gant ar valizenn RSS podcast:medium. Cheñch an dra-se a c\'hell cheñch an doare ma vo kinniget ho kwazh gant al lennerien.', + 'podcast' => 'Podkast', + 'podcast_hint' => 'Evit deskrivañ gwazh ur podkast.', + 'music' => 'Sonerezh', + 'music_hint' => 'Ur wazh gant sonerezh aozet e-barzh un "album". Pep item zo un ton en album.', + 'audiobook' => 'Levr klevet', + 'audiobook_hint' => 'Ur seurt aodio dibar gant un item dre wazh, peotramant pa glot an itemoù gant chabistroù al levr.', ], 'description' => 'Deskrivadur', 'classification_section_title' => 'Rummatadur', @@ -93,12 +106,14 @@ return [ ], 'author_section_title' => 'Aozer·ez', 'author_section_subtitle' => 'Piv zo o verañ ar podkast?', - 'owner_name' => 'Anv ar perc\'henn', + 'owner_name' => 'Anv ar perc\'henn·ez', 'owner_name_hint' => - 'Evit a sell ouzh ar mererezh. War al lanv RSS publik e vo.', - 'owner_email' => 'Chomlec\'h postel ar perc\'henn', + 'Evit a sell ouzh ar mererezh. War ar wazh RSS publik e vo.', + 'owner_email' => 'Chomlec\'h postel ar perc\'henn·ez', 'owner_email_hint' => - 'Implijet e vo gant an darn vrasañ eus ar savennoù evit gwiriañ perc\'hentiezh ar podkast. War al lanv RSS publik e vo.', + 'Implijet e vo gant an darn vrasañ eus ar savennoù evit gwiriañ perc\'hentiezh ar podkast. War ar wazh RSS publik e vo.', + 'is_owner_email_removed_from_feed' => 'Lemel chomlec\'h postel ar perc\'henn·ez diouzh ar wazh RSS publik', + 'is_owner_email_removed_from_feed_hint' => 'Rankout a rafec\'h lakaat ar chomlec\'h war wel adarre, evit ur mare, evit ma vefe gouest ur meneger da wiriañ oc\'h ar perc\'henn·ez.', 'publisher' => 'Embanner·ez', 'publisher_hint' => 'Ar strollad kiriek eus sevel ar podkast. Alies eo embregerezh pe rouedad ar podkast. A-wechoù e vez anvet ar vaezienn-mañ "Aozer·ez".', @@ -111,8 +126,13 @@ return [ 'monetization_section_subtitle' => 'Dastum arc\'hant a-drugarez d\'ho selaouerien·ezed.', 'premium' => 'Premium', - 'premium_by_default' => 'Episodes must be set as premium by default', - 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'premium_by_default' => 'Ar rannoù a zo evit ar re bremium dre ziouer', + 'premium_by_default_hint' => 'Rannoù ar podkast a vo merket Premium dre ziouer. Gallout a rit lakaat rannoù zo evel publik.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Mont da welet ho taolenn-stur OP3 (liamm diavaez)', + 'op3_hint' => 'Talvoudekait ho roadennoù gant OP3, zo ur servij open source diavaez evit dielfennañ ar muzulioù heklev. Rannit, kadarnait ha lakait ho roadennoù keñver-ha-keñver gant re ekosistem ar podkastoù digor.', + 'op3_enable' => 'Gweredekaat ar servij dielfennañ OP3', + 'op3_enable_hint' => 'Evit abegoù surentez ne vo ket rannet roadennoù ar rannoù Premium gant OP3.', 'payment_pointer' => 'Chomlec\'h paeañ (Payment Poienter) evit Web Monetization', 'payment_pointer_hint' => 'Ar chomlec\'h ma vo dastumet an arc\'hant ganeoc\'h a-drugarez da Web Monetization', @@ -121,11 +141,12 @@ return [ 'M\'ho peus ezhomm eus balizennoù RSS ha n\'eus ket anezho e Castopod e c\'hellit o lakaat amañ.', 'custom_rss' => 'Balizennoù RSS personelaet evit ar podkast', 'custom_rss_hint' => 'An dra-se a vo ouzhpennet e-barzh ar valizenn ❬channel❭.', - 'new_feed_url' => 'URL nevez al lanv', - 'new_feed_url_hint' => 'Implijit ar vaezienn-mañ pa cheñchit anv domani pe savenn herberc\'hiañ ho podkast. M\'eo enporzhiet ar podkast e vez lakaet enni URL a-vremañ al lanv dre ziouer.', - 'old_feed_url' => 'URL kozh al lanv', - 'update_feed' => 'Hizivaat al lanv', - 'update_feed_tip' => 'Enporzhiañ rannoù diwezhañ ar podkast-mañ', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', + 'new_feed_url' => 'URL nevez ar wazh', + 'new_feed_url_hint' => 'Implijit ar vaezienn-mañ pa cheñchit anv domani pe savenn herberc\'hiañ ho podkast. M\'eo enporzhiet ar podkast e vez lakaet enni URL a-vremañ ar wazh dre ziouer.', + 'old_feed_url' => 'URL kozh ar wazh', 'partnership' => 'Kevelerezh', 'partner_id' => 'ID', 'partner_link_url' => 'Ere URL', @@ -133,14 +154,13 @@ return [ 'partner_id_hint' => 'Hoc’h ID deoc\'h-c\'hwi e ti ar c\'heveler', 'partner_link_url_hint' => 'Chomlec\'h generek an ereoù gant ar c\'heveler', 'partner_image_url_hint' => 'Chomlec\'h generek ar skeudennoù gant ar c\'heveler', - 'status_section_title' => 'Statud', 'block' => 'Ar podkast a rankfe bezañ kuzhet diouzh ar rolladoù publik', 'block_hint' => 'Diskouez pe kuzhat ar podkast: trec\'haoliñ an afell-mañ a viro ar podkast a-bezh ouzh bezañ diskouezet war Apple Podcasts, Google Podcasts pe savennoù all hag a implij ar renabloù-se. (N\'eus gwarant ebet)', 'complete' => 'Ne vo mui rannoù nevez gant ar podkast', 'lock' => 'Mirout ar podkast ouzh bezañ eilet', 'lock_hint' => - 'Ar pal eo lavaret d\'ar savennoù all hag aotreet int da enporzhiañ al lanv-mañ pe get. "Ya" a dalv eo nac\'het an holl c\'houlennoù enporzhiañ.', + 'Ar pal eo lavaret d\'ar savennoù all hag aotreet int da enporzhiañ ar wazh-mañ pe get. "Ya" a dalv eo nac\'het an holl c\'houlennoù enporzhiañ.', 'submit_create' => 'Krouiñ ar podkast', 'submit_edit' => 'Enrollañ ar podkast', ], @@ -244,70 +264,74 @@ return [ 'golf' => 'Golf', 'hockey' => 'Hockey', 'rugby' => 'Rugbi', - 'running' => 'Running', - 'soccer' => 'Soccer', - 'swimming' => 'Swimming', + 'running' => 'Redek', + 'soccer' => 'Mell-droad', + 'swimming' => 'Neuierezh', 'tennis' => 'Tennis', 'volleyball' => 'Volleyball', - 'wilderness' => 'Wilderness', - 'wrestling' => 'Wrestling', - 'after_shows' => 'After Shows', - 'film_history' => 'Film History', - 'film_interviews' => 'Film Interviews', - 'film_reviews' => 'Film Reviews', - 'tv_reviews' => 'TV Reviews', + 'wilderness' => 'Natur', + 'wrestling' => 'Gouren', + 'after_shows' => 'Goude abadenn', + 'film_history' => 'Istor ar sinema', + 'film_interviews' => 'Atersadennoù er sinema', + 'film_reviews' => 'Barnadennoù filmoù', + 'tv_reviews' => 'Barnadennoù tele', ], 'publish_form' => [ - 'back_to_podcast_dashboard' => 'Back to podcast dashboard', - 'post' => 'Your announcement post', + 'back_to_podcast_dashboard' => 'Distreiñ da daolenn-stur ar podkast', + 'post' => 'Ho kemennadenn vrudañ', 'post_hint' => - "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", - 'message_placeholder' => 'Write your message…', - 'submit' => 'Publish', - 'publication_date' => 'Publication date', + "Skrivit ur gemennadenn evit kemenn embannadur ho podkast. War bajenn degemer ho podkast e vo diskouezet ar gemennadenn.", + 'message_placeholder' => 'Skrivit ho kemennadenn…', + 'submit' => 'Embann', + 'publication_date' => 'Deiziad an embann', 'publication_method' => [ - 'now' => 'Now', - 'schedule' => 'Schedule', + 'now' => 'Bremañ', + 'schedule' => 'Steuñviñ', ], - 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date' => 'Deiziad embannadur steuñvet', 'scheduled_publication_date_hint' => - 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', - 'submit_edit' => 'Edit publication', - 'cancel_publication' => 'Cancel publication', - 'message_warning' => 'You did not write a message for your announcement post!', - 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', - 'message_warning_submit' => 'Publish anyway', + 'Gallout a rit steuñviñ embannadur ar podkast en ur choaz un deiziad evit an embannadur. Dleout a ra ar vaezienn bezañ er furmad YYYY-MM-DD HH:mm', + 'submit_edit' => 'Kemmañ an embannadur', + 'cancel_publication' => 'Nullañ an embann', + 'message_warning' => 'N\'ho peus ket skrivet ur gemennadenn evit brudañ ho rann !', + 'message_warning_hint' => 'Ouzhpennañ ur gemennadenn a lakay muioc\'h a dud er jeu, ha diwar se e vo gwelet muioc\'h ho podkast.', + 'message_warning_submit' => 'Embann memestra', ], 'publication_status_banner' => [ - 'draft_mode' => 'draft mode', - 'not_published' => 'This podcast is not yet published.', - 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + 'draft_mode' => 'mod brouilhed', + 'not_published' => 'N\'eo ket embannet ar podkast-mañ c\'hoazh.', + 'scheduled' => 'Ar podkast-mañ a vo embannet d\'an/d\'ar {publication_date}.', ], 'delete_form' => [ 'disclaimer' => - "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", - 'understand' => 'I understand, I want the podcast to be permanently deleted', - 'submit' => 'Delete', + "Pa vo dilamet ar podkast e vo dilamet an holl rannoù, restroù media, kemennadennoù ha stadegoù liammet outañ. Ur wech dilamet, ne c'hell bezañ adtapet an traoù.", + 'understand' => 'Kompren a ran, c\'hoant am eus da zilemel ar podkast da vat', + 'submit' => 'Dilemel', ], - 'by' => 'By {publisher}', - 'season' => 'Season {seasonNumber}', - 'list_of_episodes_year' => '{year} episodes ({episodeCount})', + 'by' => 'Gant {publisher}', + 'season' => 'Koulzad {seasonNumber}', + 'list_of_episodes_year' => 'Rannoù {year} ({episodeCount})', 'list_of_episodes_season' => - 'Season {seasonNumber} episodes ({episodeCount})', - 'no_episode' => 'No episode found!', - 'follow' => 'Follow', + 'Rannoù ar c\'houlzad {seasonNumber} ({episodeCount})', + 'no_episode' => 'N\'eus bet kavet rann ebet !', + 'follow' => 'Heuliañ', 'followers' => '{numberOfFollowers, plural, - one {# follower} - other {# followers} + one {# heulier·ez} + two {# heulier·ez} + other {# heulier·ez} }', 'posts' => '{numberOfPosts, plural, - one {# post} - other {# posts} + one {# gemennadenn} + two {# gemennadenn} + few {# c\'hemennadenn} + many {a gemennadennoù} + other {# kemennadenn} }', - 'activity' => 'Activity', - 'episodes' => 'Episodes', - 'sponsor' => 'Sponsor', - 'funding_links' => 'Funding links for {podcastTitle}', - 'find_on' => 'Find {podcastTitle} on', + 'activity' => 'Oberiantiz', + 'episodes' => 'Rannoù', + 'sponsor' => 'Harpit ac\'hanomp', + 'funding_links' => 'Ereoù evit arc\'hantaouiñ {podcastTitle}', + 'find_on' => 'Kavit {podcastTitle} war', 'listen_on' => 'Selaouit war', ]; diff --git a/modules/Admin/Language/br/PodcastImport.php b/modules/Admin/Language/br/PodcastImport.php deleted file mode 100644 index 7ba8d909..00000000 --- a/modules/Admin/Language/br/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'The podcast to import', - 'old_podcast_section_subtitle' => - 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', - 'imported_feed_url' => 'URL al lanv', - 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', - 'new_podcast_section_title' => 'Ar podkast nevez', - 'advanced_params_section_title' => 'Arventennoù kempleshoc\'h', - 'advanced_params_section_subtitle' => - 'Keep the default values if you have no idea of what the fields are for.', - 'slug_field' => 'Field to be used to calculate episode slug', - 'description_field' => - 'Source field used for episode description / show notes', - 'force_renumber' => 'Force episodes renumbering', - 'force_renumber_hint' => - 'Use this if your podcast does not have episode numbers but wish to set them during import.', - 'season_number' => 'Niverenn ar c\'houlzad', - 'season_number_hint' => - 'Use this if your podcast does not have a season number but wish to set one during import. Leave blank otherwise.', - 'max_episodes' => 'Maximum number of episodes to import', - 'max_episodes_hint' => 'Leave blank to import all episodes', - 'lock_import' => - 'This feed is protected. You cannot import it. If you are the owner, unprotect it on the origin platform.', - 'submit' => 'Enporzhiañ ar podkast', -]; diff --git a/modules/Admin/Language/br/PodcastNavigation.php b/modules/Admin/Language/br/PodcastNavigation.php index 5c3842ca..f3d2a8e6 100644 --- a/modules/Admin/Language/br/PodcastNavigation.php +++ b/modules/Admin/Language/br/PodcastNavigation.php @@ -10,29 +10,33 @@ declare(strict_types=1); return [ 'go_to_page' => 'Mont da pajenn ar podkast', + 'rss_feed' => 'Gwazh RSS', 'dashboard' => 'Taolenn-stur ar podkast', 'podcast-view' => 'Degemer', 'podcast-edit' => 'Kemmañ ar podkast', 'podcast-persons-manage' => 'Merañ an emellerien·ezed', + 'podcast-imports' => 'Ar podkastoù enporzhiet', + 'podcast-imports-sync' => 'Sinkronaat ar gwazhoù', 'episodes' => 'Rannoù', 'episode-list' => 'An holl rannoù', 'episode-create' => 'Rann nevez', 'analytics' => 'Muzulioù heklev', - 'podcast-analytics' => 'Audience overview', - 'podcast-analytics-webpages' => 'Web pages visits', + 'podcast-analytics' => 'Gwel a-vras', + 'podcast-analytics-webpages' => 'Gweladennoù ar pajennoù web', 'podcast-analytics-locations' => 'Lec\'hioù', 'podcast-analytics-unique-listeners' => 'Selaouerien·ezed unel', 'podcast-analytics-players' => 'Lennerioù', 'podcast-analytics-listening-time' => 'Padelezh ar selaou', 'podcast-analytics-time-periods' => 'Mareoù ar selaou', - 'premium' => 'Premium', - 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', + 'monetization' => 'Moneisaat', + 'subscription-list' => 'An holl goumanantoù', + 'subscription-create' => 'Ouzhpennañ ur c\'houmanant', 'contributors' => 'Perzhidi, perzhiadezed', 'contributor-list' => 'An holl berzhidi ha perzhiadezed', 'contributor-add' => 'Ouzhpennañ ur perzhiad pe ur berzhiadez', - 'platforms' => 'Savennoù diavaez', - 'platforms-podcasting' => 'Podkast', + 'broadcast' => 'Skignañ', + 'platforms-podcasting' => 'Arloadoù podkastoù', 'platforms-social' => 'Rouedadoù sokial', - 'platforms-funding' => 'Arc\'hantaouiñ', + 'platforms-funding' => 'Liammoù arc\'hantaouiñ', + 'podcast-monetization-other' => 'Un dra all', ]; diff --git a/modules/Admin/Language/br/Settings.php b/modules/Admin/Language/br/Settings.php index 4929034f..62dab9f1 100644 --- a/modules/Admin/Language/br/Settings.php +++ b/modules/Admin/Language/br/Settings.php @@ -14,42 +14,42 @@ return [ 'title' => 'Istañs', 'site_icon' => 'Arlun al lec\'hienn', 'site_icon_delete' => 'Dilemel arlun al lec\'hienn', - 'site_icon_hint' => 'Site icons are what you see on your browser tabs, bookmarks bar, and when you add a website as a shortcut on mobile devices.', + 'site_icon_hint' => 'Arlunioù al lec\'hienn zo ar pezh a welit war ivinelloù ho merdeer, barrenn ar pennrolloù, ha pa vez ouzhpennet al lec\'hienn d\'ur pellgomzer evel ur verradenn.', 'site_icon_helper' => 'An arlun a rankfe bezañ ur c\'harrez ha 512px e vent da nebeutañ.', 'site_name' => 'Titl al lec\'hienn', 'site_description' => 'Deskrivadur al lec\'hienn', 'submit' => 'Enrollañ', - 'editSuccess' => 'Instance has been updated successfully!', - 'deleteIconSuccess' => 'Site icon has been remove successfully!', + 'editSuccess' => 'Nevesaet eo bet an istañs gant berzh!', + 'deleteIconSuccess' => 'Skarzhet eo bet arlun al lec\'hienn gant berzh!', ], 'images' => [ 'title' => 'Skeudennoù', - 'subtitle' => 'Here you can regenerate all images based on the originals that were uploaded. To be used if you find that some images are missing. This task may take a while.', - 'regenerate' => 'Regenerate images', - 'regenerationSuccess' => 'All images have been regenerated successfully!', + 'subtitle' => 'Amañ e c\'hellit azgenel an holl skeudennoù diwar ar skeudennoù orin a oa bet karget. Da vezañ implijet ma kav deoc\'h e vank skeudennoù zo. Hir a-walc\'h e c\'hell bezañ al labour-mañ.', + 'regenerate' => 'Azgenel ar skeudennoù', + 'regenerationSuccess' => 'An holl skeudennoù zo bet azganet gant berzh!', ], 'housekeeping' => [ - 'title' => 'Housekeeping', - 'subtitle' => 'Runs various housekeeping tasks. Use this feature if you ever encounter issues with media files or data integrity. These tasks may take a while.', - 'reset_counts' => 'Reset counts', - 'reset_counts_helper' => 'This option will recalculate and reset all data counts (number of followers, posts, comments, …).', - 'rewrite_media' => 'Rewrite media metadata', - 'rewrite_media_helper' => 'This option will delete all superfluous media files and recreate them (images, audio files, transcripts, chapters, …)', - 'rename_episodes_files' => 'Rename episode audio files', - 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', - 'clear_cache' => 'Clear all cache', - 'clear_cache_helper' => 'This option will flush redis cache or writable/cache files.', - 'run' => 'Run housekeeping', - 'runSuccess' => 'Housekeeping has been run successfully!', + 'title' => 'Kempenn an istañs', + 'subtitle' => 'Lañsañ labourioù a bep seurt evit kempenn an istañs. Da vezañ implijet ma welit kudennoù gant restroù media pe aterinder ar roadennoù. Hir a-walc\'h e c\'hell bezañ al labourioù-mañ.', + 'reset_counts' => 'Adderaouekaat ar c\'honterioù', + 'reset_counts_helper' => 'Gant an dibarzh-mañ e vo adjedet hag adderaouekaet ar c\'hontoù (niver a heulierien·ezed, a bostoù, a evezhiadennoù, …).', + 'rewrite_media' => 'Adskrivañ metaroadennoù ar media', + 'rewrite_media_helper' => 'Gant an dibarzh-mañ e vo skarzhet ar restroù media diezhomm hag adkrouet en-dro (skeudennoù, restroù aodio, treuzskrivadurioù, chabistroù, …)', + 'rename_episodes_files' => 'Adenvel restroù aodio ar rannoù', + 'rename_episodes_files_hint' => 'Gant an dibarzh-mañ e vo adanvet restroù aodio an holl rannoù gant ur chadenn arouezennoù dre zegouezh. Da implijout m\'eo bet kavet unan eus liammoù ho rannoù prevez, rak an dra-se a guzho anezhañ en-dro.', + 'clear_cache' => 'Naetaat ar grubuilh', + 'clear_cache_helper' => 'Gant an dibarzh-mañ e vo naetaet krubuilh Redis pe restroù an teuliad writable/cache.', + 'run' => 'Lañsañ ar c\'hempenn', + 'runSuccess' => 'Echu eo ar c\'hempenn gant berzh!', ], 'theme' => [ 'title' => 'Neuz', 'accent_section_title' => 'Liv kentañ', - 'accent_section_subtitle' => 'Choose the color to determine the look and feel of all public pages.', - 'pine' => 'Pine', - 'crimson' => 'Crimson', - 'amber' => 'Amber', - 'lake' => 'Lake', + 'accent_section_subtitle' => 'Choazit al liv a roio an neuz d\'an holl bajennoù publik.', + 'pine' => 'Pin', + 'crimson' => 'Ruz-mouk', + 'amber' => 'Goularz', + 'lake' => 'Lenn', 'jacaranda' => 'Jacaranda', 'onyx' => 'Onyx', 'submit' => 'Enrollañ', diff --git a/modules/Admin/Language/br/Validation.php b/modules/Admin/Language/br/Validation.php index 750b1968..59c49e93 100644 --- a/modules/Admin/Language/br/Validation.php +++ b/modules/Admin/Language/br/Validation.php @@ -10,9 +10,8 @@ declare(strict_types=1); return [ 'min_dims' => - '{field} is either not an image, or it is not wide or tall enough.', + '{field} n\'eo ket ur skeudenn, peotrament n\'eo ket ledan a-walc\'h pe uhel a-walc\'h.', 'is_image_ratio' => - '{field} is either not an image or not of the right ratio.', - 'validate_url' => - 'The {field} field must be a valid URL (eg. https://example.com/).', + '{field} n\'eo ket ur skeudenn, peotrament n\'eo ket mat ar ratio.', + 'is_json' => 'JSON direizh a zo e-barzh {field}.', ]; diff --git a/modules/Admin/Language/br/VideoClip.php b/modules/Admin/Language/br/VideoClip.php index fa2adf21..1ce237b3 100644 --- a/modules/Admin/Language/br/VideoClip.php +++ b/modules/Admin/Language/br/VideoClip.php @@ -10,60 +10,60 @@ declare(strict_types=1); return [ 'list' => [ - 'title' => 'Tennadoù video', + 'title' => 'Klipoù video', 'status' => [ 'label' => 'Statud', 'queued' => 'el lost', - 'queued_hint' => 'Emañ an tennad video el lost.', + 'queued_hint' => 'Emañ ar c\'hlip video el lost.', 'pending' => 'o c\'hortoz', - 'pending_hint' => 'Clip will be generated shortly.', - 'running' => 'running', - 'running_hint' => 'Clip is being generated.', + 'pending_hint' => 'Ganet e vo ar c\'hlip a-benn nebeut.', + 'running' => 'war ar stern', + 'running_hint' => 'Emañ ar c\'hlip o vezañ ganet.', 'failed' => 'c\'hwitet', - 'failed_hint' => 'Clip could not be generated: script failure.', - 'passed' => 'passed', - 'passed_hint' => 'Clip was generated successfully!', + 'failed_hint' => 'Ar c\'hlip n\'eo ket bet ganet: fazi skript.', + 'passed' => 'berzh', + 'passed_hint' => 'Ganet eo bet ar c\'hlip gant berzh!', ], - 'clip' => 'Tennad', - 'duration' => 'Job duration', + 'clip' => 'Klip', + 'duration' => 'Padelezh al labour', ], - 'title' => 'Video clip: {videoClipLabel}', - 'download_clip' => 'Pellgargañ an tennad', - 'create' => 'Tennad video nevez', - 'go_to_page' => 'Mont da pajenn an tennad', - 'retry' => 'Retry clip generation', - 'delete' => 'Dilemel an tennad', - 'logs' => 'Job logs', + 'title' => 'Klip video: {videoClipLabel}', + 'download_clip' => 'Pellgargañ ar c\'hlip', + 'create' => 'Klip video nevez', + 'go_to_page' => 'Mont da pajenn ar c\'hlip', + 'retry' => 'Klask genel ar c\'hlip en-dro', + 'delete' => 'Dilemel ar c\'hlip', + 'logs' => 'Roll istor al labourioù', 'messages' => [ - 'alreadyExistingError' => 'The video clip you are trying to create already exists!', - 'addToQueueSuccess' => 'Video clip has been added to queue, awaiting to be created!', - 'deleteSuccess' => 'Video clip has been successfully removed!', + 'alreadyExistingError' => 'Bez ez eus eus ur c\'hlip video heñvel-mik dija!', + 'addToQueueSuccess' => 'Ouzhpennet eo bet ar c\'hlip video d\'al lost, o c\'hortoz e vefe krouet!', + 'deleteSuccess' => 'Dilamet eo bet ar c\'hlip video gant berzh!', ], 'format' => [ - 'landscape' => 'Landscape', - 'portrait' => 'Portrait', - 'squared' => 'Squared', + 'landscape' => 'A-led', + 'portrait' => 'A-blom', + 'squared' => 'Karrezek', ], 'form' => [ - 'title' => 'New video clip', - 'params_section_title' => 'Video clip parameters', - 'clip_title' => 'Titl an tennad', + 'title' => 'Klip video nevez', + 'params_section_title' => 'Arventennoù ar c\'hlip video', + 'clip_title' => 'Titl ar c\'hlip', 'format' => [ - 'label' => 'Choose a format', - 'landscape_hint' => 'With a 16:9 ratio, landscape videos are great for PeerTube, Youtube and Vimeo.', - 'portrait_hint' => 'With a 9:16 ratio, portrait videos are great for TikTok, Youtube shorts and Instagram stories.', - 'squared_hint' => 'With a 1:1 ratio, squared videos are great for Mastodon, Facebook, Twitter and LinkedIn.', + 'label' => 'Choazit ur furmad', + 'landscape_hint' => 'Videoioù a-led gant ur ratio 16:9 a zo dispar evit PeerTube, Youtube ha Vimeo.', + 'portrait_hint' => 'Videoioù a-blom gant ur ratio 9:16 a zo dispar evit TikTok, Youtube shorts ha stories Instagram.', + 'squared_hint' => 'Videoioù karrezek gant ur ratio 1:1 a zo dispar evit Mastodon, Facebook, Twitter ha LinkedIn.', ], 'theme' => 'Dibab un neuz', - 'start_time' => 'Start at', - 'duration' => 'Duration', - 'trim_start' => 'Trim start', - 'trim_end' => 'Trim end', - 'submit' => 'Create video clip', + 'start_time' => 'Kregiñ da', + 'duration' => 'Padelezh', + 'trim_start' => 'Krennañ ar penn-kentañ', + 'trim_end' => 'Krennañ an dibenn', + 'submit' => 'Krouiñ ur c\'hlip video', ], 'requirements' => [ 'title' => 'Mankout a ra binvioù', - 'missing' => 'You have missing requirements. Make sure to add all the required items to be allowed creating a video for this episode!', + 'missing' => 'Mankout a ra ostilhoù. Bezit sur eo bet ouzhpennet an holl vinvioù ez ezhomm anezho evit bezañ gouest da sevel ur video diwar ar rann-mañ!', 'ffmpeg' => 'FFmpeg', 'gd' => 'Graphics Draw (GD)', 'freetype' => 'Levraoueg Freetype evit GD', diff --git a/modules/Admin/Language/ca/AboutCastopod.php b/modules/Admin/Language/ca/AboutCastopod.php new file mode 100644 index 00000000..c8598e13 --- /dev/null +++ b/modules/Admin/Language/ca/AboutCastopod.php @@ -0,0 +1,22 @@ + 'Sobre Castopod', + 'host_name' => 'Nom del servidor', + 'version' => 'Versió de Castopod', + 'php_version' => 'Versió de PHP', + 'os' => 'Sistema operatiu', + 'languages' => 'Idiomes', + 'update_database' => 'Actualitza la base de dades', + 'messages' => [ + 'databaseUpdateSuccess' => 'La base de dades està actualitzada!', + ], +]; diff --git a/modules/Admin/Language/ca/Breadcrumb.php b/modules/Admin/Language/ca/Breadcrumb.php index 9fa7362a..8beefd66 100644 --- a/modules/Admin/Language/ca/Breadcrumb.php +++ b/modules/Admin/Language/ca/Breadcrumb.php @@ -14,29 +14,34 @@ return [ ->gateway => 'Inici', 'podcasts' => 'podcasts', 'episodes' => 'episodis', - 'subscriptions' => 'subscriptions', + 'subscriptions' => 'subscripcions', 'contributors' => 'col·laboradors', 'pages' => 'pàgines', 'settings' => 'preferències', 'theme' => 'tema', + 'about' => 'quant a', 'add' => 'afegir', 'new' => 'nova', 'edit' => 'editar', 'persons' => 'persones', 'publish' => 'publicar', 'publish-edit' => 'editar la publicació', - 'publish-date-edit' => 'edit publication date', + 'publish-date-edit' => 'edita la data de publicació', 'unpublish' => 'desfer la publicació', 'delete' => 'eliminar', + 'remove' => 'suprimeix', 'fediverse' => 'Fediverse', - 'block-lists' => 'llista de bloquejats', + 'blocked-actors' => 'comptes bloquejats', + 'blocked-domains' => 'dominis bloquejats', 'users' => 'usuaris', 'my-account' => 'el meu compte', 'change-password' => 'canviar la contrasenya', - 'import' => 'importar un feed', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', 'platforms' => 'plataformes', 'social' => 'xarxes socials', 'funding' => 'financiació', + 'monetization-other' => 'other monetization', 'analytics' => 'estadístiques', 'locations' => 'ubicacions', 'webpages' => 'pàgines web', @@ -48,5 +53,5 @@ return [ 'video-clips' => 'vídeoclips', 'embed' => 'reproductor incrustable', 'notifications' => 'notificacions', - 'suspend' => 'suspend', + 'suspend' => 'suspèn', ]; diff --git a/modules/Admin/Language/ca/Charts.php b/modules/Admin/Language/ca/Charts.php index 4873a6ee..b1085117 100644 --- a/modules/Admin/Language/ca/Charts.php +++ b/modules/Admin/Language/ca/Charts.php @@ -37,4 +37,5 @@ return [ 'podcast_by_bandwidth' => 'Ample de banda emprat diàriament (en MB)', 'total_storage_by_month' => 'Emmagatzematge mensual (en MB)', 'total_bandwidth_by_month' => 'Ample de banda emprat mensualment (en MB)', + 'total_bandwidth_by_month_limit' => 'Limitat a {totalBandwidth} al mes', ]; diff --git a/modules/Admin/Language/ca/Common.php b/modules/Admin/Language/ca/Common.php index c178db60..8ce267d5 100644 --- a/modules/Admin/Language/ca/Common.php +++ b/modules/Admin/Language/ca/Common.php @@ -40,6 +40,7 @@ return [ ], 'upload_file' => 'Pujar un fitxer', 'remote_url' => 'URL remota', + 'save' => 'Save', ], 'play_episode_button' => [ 'play' => 'Reproduir', diff --git a/modules/Admin/Language/ca/Episode.php b/modules/Admin/Language/ca/Episode.php index ed05a8d7..ba0ff1f2 100644 --- a/modules/Admin/Language/ca/Episode.php +++ b/modules/Admin/Language/ca/Episode.php @@ -22,16 +22,17 @@ return [ 'all_podcast_episodes' => 'Tots els episodis del podcast', 'back_to_podcast' => 'Tornar al podcast', 'edit' => 'Editar', + 'preview' => 'Preview', 'publish' => 'Publicar', 'publish_edit' => 'Editar la publicació', - 'publish_date_edit' => 'Edit publication date', + 'publish_date_edit' => 'Edita la data de publicació', 'unpublish' => 'Desfer la publicació', 'publish_error' => 'L\'episodi ja està publicat.', 'publish_edit_error' => 'L\'episodi ja està publicat.', 'publish_cancel_error' => 'L\'episodi ja està publicat.', - 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', - 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', - 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'publish_date_edit_error' => 'L\'episodi encara no s\'ha publicat, no podeu editar-ne la data de publicació.', + 'publish_date_edit_future_error' => 'La data de publicació de l\'episodi només es pot establir en una data passada! Si voleu reprogramar-lo, cancel·leu-lo primer.', + 'publish_date_edit_success' => 'La data de publicació de l\'episodi ha estat actualitzada correctament!', 'unpublish_error' => 'L\'episodi no està publicat.', 'delete' => 'Eliminar', 'go_to_page' => 'Anar a la pàgina ', @@ -55,6 +56,7 @@ return [ }', 'episode' => 'Episodi', 'visibility' => 'Visibilitat', + 'downloads' => 'Downloads', 'comments' => 'Comentaris', 'actions' => 'Accions', ], @@ -79,13 +81,13 @@ return [ audio {l\'àudio} other {el material} } de l\'episodi.', - 'deleteFileError' => 'No s\'ha pogut esborrar el fitxer {file_path} {type, select, - transcript {de la transcripció} - chapters {dels episodis} - image {de la portada} - audio {de l\'àudio} - other {del material} - }. Podeu esborrar-los manualment del disc.', + 'deleteFileError' => 'Failed to delete {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'Ja existeix un episodi amb aquest àlies.', ], 'form' => [ @@ -113,8 +115,8 @@ return [ 'bonus' => 'Bonificació', 'bonus_hint' => 'Contingut addicional per al programa (per exemple, informació entre bastidors o entrevistes amb el repartiment) o contingut promocional creuat per a un altre programa', ], - 'premium_title' => 'Premium', - 'premium' => 'Episode must be accessible to premium subscribers only', + 'premium_title' => 'Prèmium', + 'premium' => 'L\'episodi ha de ser accessible només per a subscriptors prèmium', 'parental_advisory' => [ 'label' => 'Avís parental', 'hint' => 'L\'episodi conté contingut explícit?', @@ -137,9 +139,9 @@ return [ 'location_name' => 'Nom i adreça de la ubicació', 'location_name_hint' => 'Pot ser una ubicació real o fictícia', 'transcript' => 'Transcripció (subtítols)', - 'transcript_hint' => 'Només es permet fitxers .srt', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', 'transcript_download' => 'Baixar la transcripció', - 'transcript_file' => 'Fitxer de la transcripció (.srt)', + 'transcript_file' => 'Transcript file (.srt or .vtt)', 'transcript_remote_url' => 'URL remota per a la transcripció', 'transcript_file_delete' => 'Eliminar el fitxer de la transcripció', 'chapters' => 'Capítols', @@ -183,9 +185,9 @@ return [ 'message_warning_submit' => 'Publicar de totes maneres', ], 'publish_date_edit_form' => [ - 'new_publication_date' => 'New publication date', - 'new_publication_date_hint' => 'Must be set to a past date.', - 'submit' => 'Edit publication date', + 'new_publication_date' => 'Nova data de publicació', + 'new_publication_date_hint' => 'Has de posar una data passada.', + 'submit' => 'Edita la data de publicació', ], 'unpublish_form' => [ 'disclaimer' => @@ -210,4 +212,14 @@ return [ 'light' => 'Clar', 'light-transparent' => 'Clar i transparent', ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], ]; diff --git a/modules/Admin/Language/ca/Navigation.php b/modules/Admin/Language/ca/Navigation.php index 77b48aee..5fc3659b 100644 --- a/modules/Admin/Language/ca/Navigation.php +++ b/modules/Admin/Language/ca/Navigation.php @@ -12,12 +12,14 @@ return [ 'toggle_sidebar' => 'Ocultar/mostrar barra lateral', 'go_to_website' => 'Anar al lloc web', 'go_to_admin' => 'Anar al panell de control', + 'not-authorized' => 'Not authorized', 'dashboard' => 'Panell de control', 'admin' => 'Inici', 'podcasts' => 'Podcasts', 'podcast-list' => 'Tots els podcasts', 'podcast-create' => 'Nou podcast', - 'podcast-import' => 'Importar un podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', 'persons' => 'Persones', 'person-list' => 'Totes les persones', 'person-create' => 'Persona nova', @@ -33,6 +35,7 @@ return [ 'settings' => 'Preferències', 'settings-general' => 'General', 'settings-theme' => 'Tema', + 'admin-about' => 'About', 'account' => [ 'my-account' => 'El meu compte', 'change-password' => 'Canviar la contrasenya', diff --git a/modules/Admin/Language/ca/Notifications.php b/modules/Admin/Language/ca/Notifications.php index bd1849f5..35032ab1 100644 --- a/modules/Admin/Language/ca/Notifications.php +++ b/modules/Admin/Language/ca/Notifications.php @@ -13,7 +13,7 @@ return [ 'reply' => '{actor_username} ha respost a la vostra publicació', 'favourite' => '{actor_username} ha marcat com a preferit la vostra publicació', 'reblog' => '{actor_username} ha compartit la vostra publicació', - 'follow' => '{actor_username} started following you', + 'follow' => '{actor_username} t\'ha començat a seguir', 'no_notifications' => 'Cap notificació', 'mark_all_as_read' => 'Marca tot com a llegit', ]; diff --git a/modules/Admin/Language/ca/Platforms.php b/modules/Admin/Language/ca/Platforms.php index cb1abfff..cfa50efb 100644 --- a/modules/Admin/Language/ca/Platforms.php +++ b/modules/Admin/Language/ca/Platforms.php @@ -9,9 +9,22 @@ declare(strict_types=1); */ return [ - 'title' => 'Plataformes', + 'title' => [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', 'home_url' => 'Visitar el lloc web de {platformName}', + 'register' => 'Register', 'submit_url' => 'Enviar el vostre podcast a {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', 'visible' => 'Mostrar a la pàgina d\'inici del podcast?', 'on_embed' => 'Mostrar al reproductor incrustable?', 'remove' => 'Suprimir {platformName}', diff --git a/modules/Admin/Language/ca/Podcast.php b/modules/Admin/Language/ca/Podcast.php index 71772510..022e8797 100644 --- a/modules/Admin/Language/ca/Podcast.php +++ b/modules/Admin/Language/ca/Podcast.php @@ -13,6 +13,7 @@ return [ 'no_podcast' => 'No s\'han trobat podcasts!', 'create' => 'Crear un podcast', 'import' => 'Importar el podcast', + 'all_imports' => 'Podcast imports', 'new_episode' => 'Nou episodi', 'view' => 'Veure el podcast', 'edit' => 'Editar el podcast', @@ -21,6 +22,7 @@ return [ 'delete' => 'Suprimir el podcast', 'see_episodes' => 'Veure els episodis', 'see_contributors' => 'Veure els col·laboradors', + 'monetization_other' => 'Other monetization', 'go_to_page' => 'Anar a la pàgina ', 'latest_episodes' => 'Darrers episodis', 'see_all_episodes' => 'Veure tots els episodis', @@ -48,7 +50,6 @@ return [ other {s\'han afegit # episodis} } al podcast.', 'podcastFeedUpToDate' => 'El podcast ja està actualitzat.', - 'podcastNotImported' => 'No s\'ha pogut actualitzar el podcast perquè no s\'havia importat.', 'publishError' => 'Aquest podcast ja està publicat o bé està programat per a la seva publicació.', 'publishEditError' => 'Aquest podcast no està programat per a la seva publicació.', 'publishCancelSuccess' => 'La publicació del podcast s\'ha cancel·lat correctament.', @@ -57,6 +58,8 @@ return [ 'form' => [ 'identity_section_title' => 'Identitat del podcast', 'identity_section_subtitle' => 'Aquests camps permeten fer-se notar.', + 'fediverse_section_title' => 'Fediverse identity', + 'cover' => 'Portada del podcast', 'cover_size_hint' => 'La portada ha de ser quadrada i com a mínim de 1400 px d\'amplada i alçada.', 'banner' => 'Bàner del podcast', @@ -71,7 +74,17 @@ return [ 'episodic' => 'Episòdic', 'episodic_hint' => 'Si els episodis estan pensats per ser consumits sense cap ordre específic. Els episodis més recents es presentaran primer.', 'serial' => 'En sèrie', - 'serial_hint' => 'Si els episodis estan pensats per ser consumits en ordre seqüencial. Primer es presentaran els episodis més antics.', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', ], 'description' => 'Descripció', 'classification_section_title' => 'Classificació', @@ -96,6 +109,8 @@ return [ 'owner_email' => 'Correu electrònic del propietari', 'owner_email_hint' => 'La majoria de plataformes l\'utilitzaran per verificar la propietat del podcast. Visible al fil RSS públic.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', 'publisher' => 'Editor', 'publisher_hint' => 'El grup encarregat de crear el programa. Sovint es refereix a l\'empresa matriu o a la xarxa d\'un podcast. Aquest camp de vegades s\'etiqueta com a "Autor".', @@ -107,9 +122,14 @@ return [ 'monetization_section_title' => 'Monetització', 'monetization_section_subtitle' => 'Guanyeu diners gràcies al vostre públic.', - 'premium' => 'Premium', - 'premium_by_default' => 'Episodes must be set as premium by default', - 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'premium' => 'Prèmium', + 'premium_by_default' => 'Els episodis s\'han d\'establir com a prèmium de manera predeterminada', + 'premium_by_default_hint' => 'Els episodis de pòdcast es marcaran com a prèmium de manera predeterminada. Encara podreu escollir configurar alguns episodis, tràilers o bonificacions com a públics.', + 'op3' => 'Projecte obert de prefix de pòdcast (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Valoreu les vostres dades estadístiques amb OP3, un servei d\'anàlisi de tercers de codi obert i de confiança. Compartiu, valideu i compareu les vostres dades d\'anàlisi amb l\'ecosistema de podcasting obert.', + 'op3_enable' => 'Habilita el servei d\'estadístiques OP3', + 'op3_enable_hint' => 'Per motius de seguretat, les dades d\'anàlisi dels episodis prèmium no es compartiran amb OP3.', 'payment_pointer' => '`Payment Pointer` per a `Web Monetization`', 'payment_pointer_hint' => 'Aquí és on rebreu diners gràcies al servei `Web Monetization`', @@ -118,11 +138,12 @@ return [ 'Si necessiteu etiquetes RSS que Castopod no manega, configureu-les aquí.', 'custom_rss' => 'Etiquetes RSS personalitzades per al podcast', 'custom_rss_hint' => 'Això s\'injectarà dins de l\'etiqueta ❬channel❭.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', 'new_feed_url' => 'Nova adreça URL del fil RSS', 'new_feed_url_hint' => 'Utilitzeu aquest camp quan us moveu a un altre domini o plataforma d\'allotjament de podcasts. De manera predeterminada, el valor s\'estableix a l\'URL RSS actual si s\'importa el podcast.', 'old_feed_url' => 'Antiga adreça URL del fil RSS', - 'update_feed' => 'Actualitzar el fil', - 'update_feed_tip' => 'Importar els darrers episodis d\'aquest podcast', 'partnership' => 'Socis', 'partner_id' => 'ID', 'partner_link_url' => 'URL de l\'enllaç', @@ -130,7 +151,6 @@ return [ 'partner_id_hint' => 'El vostre propi ID de soci', 'partner_link_url_hint' => 'L\'adreça genèrica de l\'enllaç del soci', 'partner_image_url_hint' => 'L\'adreça genèrica de la imatge del soci', - 'status_section_title' => 'Estat', 'block' => 'El podcast s\'ha d\'amagar als catàlegs públics', 'block_hint' => 'L\'estat de visibilitat del podcast: activar aquesta opció evita que el podcast aparegui a Apple Podcasts, Google Podcasts i qualsevol aplicació de tercers que extreu programes d\'aquests directoris. (No garantit)', diff --git a/modules/Admin/Language/ca/PodcastImport.php b/modules/Admin/Language/ca/PodcastImport.php deleted file mode 100644 index 81de5fdf..00000000 --- a/modules/Admin/Language/ca/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'Aquest procediment pot trigar molt de temps. Com que la versió actual no mostra cap progrés mentre s\'executa, no veureu res actualitzat fins que no s\'hagi fet. En cas d\'error de temps d\'espera, augmenteu el valor `max_execution_time` a la configuració del PHP del servidor.', - 'old_podcast_section_title' => 'El podcast a importar', - 'old_podcast_section_subtitle' => - 'Assegura\'t de tenir els drets d\'aquest podcast abans d\'importar-lo. Copiar i difondre un podcast sense els drets adequats és pirateria i pot ser processat.', - 'imported_feed_url' => 'Adreça URL del fil', - 'imported_feed_url_hint' => 'El contingut del fil ha d\'estar en format xml o rss.', - 'new_podcast_section_title' => 'El nou podcast', - 'advanced_params_section_title' => 'Paràmetres avançats', - 'advanced_params_section_subtitle' => - 'Mantingueu els valors predeterminats si no teniu idea de per a què serveixen els camps.', - 'slug_field' => 'Camp que s\'utilitzarà per calcular l\'àlies d\'un episodi', - 'description_field' => - 'Camp d\'origen utilitzat per a la descripció de l\'episodi / notes del programa', - 'force_renumber' => 'Forçar la renumeració dels episodis', - 'force_renumber_hint' => - 'Utilitzeu aquesta funcionalitat si el vostre podcast no té números d\'episodi però voleu configurar-los durant la importació.', - 'season_number' => 'Número de temporada', - 'season_number_hint' => - 'Utilitzeu aquesta opció si el vostre podcast no té un número de temporada però voleu establir-ne un durant la importació. Deixeu en blanc en cas contrari.', - 'max_episodes' => 'Nombre màxim d\'episodis per importar', - 'max_episodes_hint' => 'Deixeu en blanc per importar tots els episodis', - 'lock_import' => - 'Aquest feed està protegit. No el podeu importar. Si sou el propietari, desprotegiu-lo a la plataforma d\'origen.', - 'submit' => 'Importar el podcast', -]; diff --git a/modules/Admin/Language/ca/PodcastNavigation.php b/modules/Admin/Language/ca/PodcastNavigation.php index e2e57dfb..bcbc8120 100644 --- a/modules/Admin/Language/ca/PodcastNavigation.php +++ b/modules/Admin/Language/ca/PodcastNavigation.php @@ -10,10 +10,13 @@ declare(strict_types=1); return [ 'go_to_page' => 'Anar a la pàgina del podcast', + 'rss_feed' => 'RSS feed', 'dashboard' => 'Panell de control del podcast', 'podcast-view' => 'Inici', 'podcast-edit' => 'Editar el podcast', 'podcast-persons-manage' => 'Administrar persones', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', 'episodes' => 'Episodis', 'episode-list' => 'Tots els episodis', 'episode-create' => 'Nou episodi', @@ -25,14 +28,15 @@ return [ 'podcast-analytics-players' => 'Reproductors', 'podcast-analytics-listening-time' => 'Temps d\'escolta', 'podcast-analytics-time-periods' => 'Períodes de temps', - 'premium' => 'Premium', - 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', + 'monetization' => 'Monetization', + 'subscription-list' => 'Totes les subscripcions', + 'subscription-create' => 'Add subscription', 'contributors' => 'Col·laboradors', 'contributor-list' => 'Tots els col·laboradors', 'contributor-add' => 'Afegir un col·laborador', - 'platforms' => 'Plataformes de tercers', - 'platforms-podcasting' => 'Podcasts', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', 'platforms-social' => 'Xarxes socials', - 'platforms-funding' => 'Financiació', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', ]; diff --git a/modules/Admin/Language/ca/Validation.php b/modules/Admin/Language/ca/Validation.php index 6740204a..fc83ea02 100644 --- a/modules/Admin/Language/ca/Validation.php +++ b/modules/Admin/Language/ca/Validation.php @@ -13,6 +13,5 @@ return [ '{field} no és una imatge, o no és prou ample o alt.', 'is_image_ratio' => '{field} no és una imatge o no té la proporció correcta.', - 'validate_url' => - 'El camp {field} ha de ser una adreça URL vàlida (p. ex., https://exemple.com/).', + 'is_json' => '{field} contains invalid JSON.', ]; diff --git a/modules/Admin/Language/da/AboutCastopod.php b/modules/Admin/Language/da/AboutCastopod.php new file mode 100644 index 00000000..7ac4aeca --- /dev/null +++ b/modules/Admin/Language/da/AboutCastopod.php @@ -0,0 +1,22 @@ + 'Om Castopod', + 'host_name' => 'Værtsnavn', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operativsystem', + 'languages' => 'Sprog', + 'update_database' => 'Opdater databasen', + 'messages' => [ + 'databaseUpdateSuccess' => 'Databasen er opdateret!', + ], +]; diff --git a/modules/Admin/Language/da/Breadcrumb.php b/modules/Admin/Language/da/Breadcrumb.php new file mode 100644 index 00000000..f02c83ac --- /dev/null +++ b/modules/Admin/Language/da/Breadcrumb.php @@ -0,0 +1,57 @@ + 'brødkrumme', + config('Admin') + ->gateway => 'Hjem', + 'podcasts' => 'podcasts', + 'episodes' => 'episoder', + 'subscriptions' => 'abonnementer', + 'contributors' => 'bidragydere', + 'pages' => 'sider', + 'settings' => 'indstillinger', + 'theme' => 'tema', + 'about' => 'om', + 'add' => 'tilføj', + 'new' => 'ny', + 'edit' => 'redigér', + 'persons' => 'personer', + 'publish' => 'publicér', + 'publish-edit' => 'redigér udgivelse', + 'publish-date-edit' => 'redigér udgivelsesdato', + 'unpublish' => 'afpublicér', + 'delete' => 'slet', + 'remove' => 'fjern', + 'fediverse' => 'fediverset', + 'blocked-actors' => 'blokerede aktører', + 'blocked-domains' => 'blokerede domæner', + 'users' => 'brugere', + 'my-account' => 'min konto', + 'change-password' => 'skift adgangskode', + 'imports' => 'importer', + 'sync-feeds' => 'synchronize feeds', + 'platforms' => 'platforme', + 'social' => 'sociale netværk', + 'funding' => 'finansiering', + 'monetization-other' => 'other monetization', + 'analytics' => 'analyse', + 'locations' => 'lokationer', + 'webpages' => 'websider', + 'unique-listeners' => 'unikke lyttere', + 'players' => 'afspillere', + 'listening-time' => 'lyttetid', + 'time-periods' => 'periode', + 'soundbites' => 'lydbid', + 'video-clips' => 'videoklip', + 'embed' => 'integreret afspiller', + 'notifications' => 'notifications', + 'suspend' => 'suspend', +]; diff --git a/modules/Admin/Language/da/Charts.php b/modules/Admin/Language/da/Charts.php new file mode 100644 index 00000000..6ede2510 --- /dev/null +++ b/modules/Admin/Language/da/Charts.php @@ -0,0 +1,41 @@ + 'Episode downloads by service (for the past week)', + 'by_player_weekly' => 'Episode downloads by player (for the past week)', + 'by_player_yearly' => 'Episode downloads by player (for the past year)', + 'by_device_weekly' => 'Episode downloads by device (for the past week)', + 'by_os_weekly' => 'Episode downloads by O.S. (for the past week)', + 'podcast_by_region' => 'Episode downloads by region (for the past week)', + 'unique_daily_listeners' => 'Daily unique listeners', + 'unique_monthly_listeners' => 'Monthly unique listeners', + 'by_browser' => 'Web pages usage by browser (for the past week)', + 'podcast_by_day' => 'Episode daily downloads', + 'podcast_by_month' => 'Episode monthly downloads', + 'episode_by_day' => 'Episode daily downloads (first 60 days)', + 'episode_by_month' => 'Episode monthly downloads', + 'episodes_by_day' => + '5 latest episodes downloads (during their first 60 days)', + 'by_country_weekly' => 'Episode downloads by country (for the past week)', + 'by_country_yearly' => 'Episode downloads by country (for the past year)', + 'by_domain_weekly' => 'Web pages visits by source (for the past week)', + 'by_domain_yearly' => 'Web pages visits by source (for the past year)', + 'by_entry_page' => 'Web pages visits by landing page (for the past week)', + 'podcast_bots' => 'Bots (crawlers)', + 'daily_listening_time' => 'Daily cumulative listening time', + 'monthly_listening_time' => 'Monthly cumulative listening time', + 'by_weekday' => 'By week day (for the past 60 days)', + 'by_hour' => 'By time of day (for the past 60 days)', + 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', + 'total_storage_by_month' => 'Monthly storage (in MB)', + 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', +]; diff --git a/modules/Admin/Language/da/Common.php b/modules/Admin/Language/da/Common.php new file mode 100644 index 00000000..b53eff26 --- /dev/null +++ b/modules/Admin/Language/da/Common.php @@ -0,0 +1,52 @@ + 'Yes', + 'no' => 'No', + 'cancel' => 'Cancel', + 'optional' => 'Optional', + 'more' => 'More', + 'no_data' => 'No data found!', + 'close' => 'Close', + 'edit' => 'Edit', + 'copy' => 'Copy', + 'copied' => 'Copied!', + 'home' => 'Home', + 'explicit' => 'Explicit', + 'powered_by' => 'Powered by {castopod}', + 'actions' => 'Actions', + 'pageInfo' => 'Page {currentPage} out of {pageCount}', + 'go_back' => 'Go back', + 'forms' => [ + 'editor' => [ + 'write' => 'Write', + 'preview' => 'Preview', + 'help' => 'Powered by markdown', + ], + 'multiSelect' => [ + 'selectText' => 'Press to select', + 'loadingText' => 'Indlæser…', + 'noResultsText' => 'No results found', + 'noChoicesText' => 'No choices to choose from', + 'maxItemText' => 'Cannot add more items', + ], + 'upload_file' => 'Upload a file', + 'remote_url' => 'Remote URL', + 'save' => 'Save', + ], + 'play_episode_button' => [ + 'play' => 'Play', + 'playing' => 'Playing', + ], + 'size_limit' => 'Size limit: {0}.', + 'choose_interact' => 'Choose how to interact', + 'view' => 'View', +]; diff --git a/modules/Admin/Language/da/Countries.php b/modules/Admin/Language/da/Countries.php new file mode 100644 index 00000000..4cd5d9c8 --- /dev/null +++ b/modules/Admin/Language/da/Countries.php @@ -0,0 +1,264 @@ + 'Andorra', + 'AE' => 'United Arab Emirates', + 'AF' => 'Afghanistan', + 'AG' => 'Antigua and Barbuda', + 'AI' => 'Anguilla', + 'AL' => 'Albania', + 'AM' => 'Armenia', + 'AO' => 'Angola', + 'AQ' => 'Antarctica', + 'AR' => 'Argentina', + 'AS' => 'American Samoa', + 'AT' => 'Austria', + 'AU' => 'Australia', + 'AW' => 'Aruba', + 'AX' => 'Åland Islands', + 'AZ' => 'Azerbaijan', + 'BA' => 'Bosnia and Herzegovina', + 'BB' => 'Barbados', + 'BD' => 'Bangladesh', + 'BE' => 'Belgium', + 'BF' => 'Burkina Faso', + 'BG' => 'Bulgaria', + 'BH' => 'Bahrain', + 'BI' => 'Burundi', + 'BJ' => 'Benin', + 'BL' => 'Saint Barthélemy', + 'BM' => 'Bermuda', + 'BN' => 'Brunei Darussalam', + 'BO' => 'Bolivia, Plurinational State of', + 'BQ' => 'Bonaire, Sint Eustatius and Saba', + 'BR' => 'Brazil', + 'BS' => 'Bahamas', + 'BT' => 'Bhutan', + 'BV' => 'Bouvet Island', + 'BW' => 'Botswana', + 'BY' => 'Belarus', + 'BZ' => 'Belize', + 'CA' => 'Canada', + 'CC' => 'Cocos (Keeling) Islands', + 'CD' => 'Congo, the Democratic Republic of the', + 'CF' => 'Central African Republic', + 'CG' => 'Congo', + 'CH' => 'Switzerland', + 'CI' => "Côte d'Ivoire", + 'CK' => 'Cook Islands', + 'CL' => 'Chile', + 'CM' => 'Cameroon', + 'CN' => 'China', + 'CO' => 'Colombia', + 'CR' => 'Costa Rica', + 'CU' => 'Cuba', + 'CV' => 'Cape Verde', + 'CW' => 'Curaçao', + 'CX' => 'Christmas Island', + 'CY' => 'Cyprus', + 'CZ' => 'Czech Republic', + 'DE' => 'Germany', + 'DJ' => 'Djibouti', + 'DK' => 'Denmark', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'DZ' => 'Algeria', + 'EC' => 'Ecuador', + 'EE' => 'Estonia', + 'EG' => 'Egypt', + 'EH' => 'Western Sahara', + 'ER' => 'Eritrea', + 'ES' => 'Spain', + 'ET' => 'Ethiopia', + 'FI' => 'Finland', + 'FJ' => 'Fiji', + 'FK' => 'Falkland Islands (Malvinas)', + 'FM' => 'Micronesia, Federated States of', + 'FO' => 'Faroe Islands', + 'FR' => 'France', + 'GA' => 'Gabon', + 'GB' => 'United Kingdom', + 'GD' => 'Grenada', + 'GE' => 'Georgia', + 'GF' => 'French Guiana', + 'GG' => 'Guernsey', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GL' => 'Greenland', + 'GM' => 'Gambia', + 'GN' => 'Guinea', + 'GP' => 'Guadeloupe', + 'GQ' => 'Equatorial Guinea', + 'GR' => 'Greece', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'GT' => 'Guatemala', + 'GU' => 'Guam', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HK' => 'Hong Kong', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HR' => 'Croatia', + 'HT' => 'Haiti', + 'HU' => 'Hungary', + 'ID' => 'Indonesia', + 'IE' => 'Ireland', + 'IL' => 'Israel', + 'IM' => 'Isle of Man', + 'IN' => 'India', + 'IO' => 'British Indian Ocean Territory', + 'IQ' => 'Iraq', + 'IR' => 'Iran, Islamic Republic of', + 'IS' => 'Iceland', + 'IT' => 'Italy', + 'JE' => 'Jersey', + 'JM' => 'Jamaica', + 'JO' => 'Jordan', + 'JP' => 'Japan', + 'KE' => 'Kenya', + 'KG' => 'Kyrgyzstan', + 'KH' => 'Cambodia', + 'KI' => 'Kiribati', + 'KM' => 'Comoros', + 'KN' => 'Saint Kitts and Nevis', + 'KP' => "Korea, Democratic People's Republic of", + 'KR' => 'Korea, Republic of', + 'KW' => 'Kuwait', + 'KY' => 'Cayman Islands', + 'KZ' => 'Kazakhstan', + 'LA' => "Lao People's Democratic Republic", + 'LB' => 'Lebanon', + 'LC' => 'Saint Lucia', + 'LI' => 'Liechtenstein', + 'LK' => 'Sri Lanka', + 'LR' => 'Liberia', + 'LS' => 'Lesotho', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'LV' => 'Latvia', + 'LY' => 'Libya', + 'MA' => 'Morocco', + 'MC' => 'Monaco', + 'MD' => 'Moldova, Republic of', + 'ME' => 'Montenegro', + 'MF' => 'Saint Martin (French part)', + 'MG' => 'Madagascar', + 'MH' => 'Marshall Islands', + 'MK' => 'Macedonia, the Former Yugoslav Republic of', + 'ML' => 'Mali', + 'MM' => 'Myanmar', + 'MN' => 'Mongolia', + 'MO' => 'Macao', + 'MP' => 'Northern Mariana Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MS' => 'Montserrat', + 'MT' => 'Malta', + 'MU' => 'Mauritius', + 'MV' => 'Maldives', + 'MW' => 'Malawi', + 'MX' => 'Mexico', + 'MY' => 'Malaysia', + 'MZ' => 'Mozambique', + 'N/A' => 'Not Applicable (local IP…)', + 'NA' => 'Namibia', + 'NC' => 'New Caledonia', + 'NE' => 'Niger', + 'NF' => 'Norfolk Island', + 'NG' => 'Nigeria', + 'NI' => 'Nicaragua', + 'NL' => 'Netherlands', + 'NO' => 'Norway', + 'NP' => 'Nepal', + 'NR' => 'Nauru', + 'NU' => 'Niue', + 'NZ' => 'New Zealand', + 'OM' => 'Oman', + 'PA' => 'Panama', + 'PE' => 'Peru', + 'PF' => 'French Polynesia', + 'PG' => 'Papua New Guinea', + 'PH' => 'Philippines', + 'PK' => 'Pakistan', + 'PL' => 'Poland', + 'PM' => 'Saint Pierre and Miquelon', + 'PN' => 'Pitcairn', + 'PR' => 'Puerto Rico', + 'PS' => 'Palestine, State of', + 'PT' => 'Portugal', + 'PW' => 'Palau', + 'PY' => 'Paraguay', + 'QA' => 'Qatar', + 'RE' => 'Réunion', + 'RO' => 'Romania', + 'RS' => 'Serbia', + 'RU' => 'Russian Federation', + 'RW' => 'Rwanda', + 'SA' => 'Saudi Arabia', + 'SB' => 'Solomon Islands', + 'SC' => 'Seychelles', + 'SD' => 'Sudan', + 'SE' => 'Sweden', + 'SG' => 'Singapore', + 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', + 'SI' => 'Slovenia', + 'SJ' => 'Svalbard and Jan Mayen', + 'SK' => 'Slovakia', + 'SL' => 'Sierra Leone', + 'SM' => 'San Marino', + 'SN' => 'Senegal', + 'SO' => 'Somalia', + 'SR' => 'Suriname', + 'SS' => 'South Sudan', + 'ST' => 'Sao Tome and Principe', + 'SV' => 'El Salvador', + 'SX' => 'Sint Maarten (Dutch part)', + 'SY' => 'Syrian Arab Republic', + 'SZ' => 'Swaziland', + 'TC' => 'Turks and Caicos Islands', + 'TD' => 'Chad', + 'TF' => 'French Southern Territories', + 'TG' => 'Togo', + 'TH' => 'Thailand', + 'TJ' => 'Tajikistan', + 'TK' => 'Tokelau', + 'TL' => 'Timor-Leste', + 'TM' => 'Turkmenistan', + 'TN' => 'Tunisia', + 'TO' => 'Tonga', + 'TR' => 'Turkey', + 'TT' => 'Trinidad and Tobago', + 'TV' => 'Tuvalu', + 'TW' => 'Taiwan, Province of China', + 'TZ' => 'Tanzania, United Republic of', + 'UA' => 'Ukraine', + 'UG' => 'Uganda', + 'UM' => 'United States Minor Outlying Islands', + 'US' => 'United States', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VA' => 'Holy See (Vatican City State)', + 'VC' => 'Saint Vincent and the Grenadines', + 'VE' => 'Venezuela, Bolivarian Republic of', + 'VG' => 'Virgin Islands, British', + 'VI' => 'Virgin Islands, U.S.', + 'VN' => 'Viet Nam', + 'VU' => 'Vanuatu', + 'WF' => 'Wallis and Futuna', + 'WS' => 'Samoa', + 'YE' => 'Yemen', + 'YT' => 'Mayotte', + 'ZA' => 'South Africa', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', +]; diff --git a/modules/Admin/Language/nn-NO/Dashboard.php b/modules/Admin/Language/da/Dashboard.php similarity index 100% rename from modules/Admin/Language/nn-NO/Dashboard.php rename to modules/Admin/Language/da/Dashboard.php diff --git a/modules/Admin/Language/da/Episode.php b/modules/Admin/Language/da/Episode.php new file mode 100644 index 00000000..7fad23ff --- /dev/null +++ b/modules/Admin/Language/da/Episode.php @@ -0,0 +1,225 @@ + 'Season {seasonNumber}', + 'season_abbr' => 'S{seasonNumber}', + 'number' => 'Episode {episodeNumber}', + 'number_abbr' => 'Ep. {episodeNumber}', + 'season_episode' => 'Season {seasonNumber} episode {episodeNumber}', + 'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}', + 'number_of_comments' => '{numberOfComments, plural, + one {# comment} + other {# comments} + }', + 'all_podcast_episodes' => 'All podcast episodes', + 'back_to_podcast' => 'Go back to podcast', + 'edit' => 'Edit', + 'preview' => 'Preview', + 'publish' => 'Publish', + 'publish_edit' => 'Edit publication', + 'publish_date_edit' => 'Edit publication date', + 'unpublish' => 'Unpublish', + 'publish_error' => 'Episode is already published.', + 'publish_edit_error' => 'Episoden er allerede publiceret.', + 'publish_cancel_error' => 'Episoden er allerede publiceret.', + 'publish_date_edit_error' => 'Episode er endnu ikke blevet offentliggjort. Du kan ikke redigere dens publiceringsdato.', + 'publish_date_edit_future_error' => 'Episodes publiceringsdato kan kun indstilles til en tidligere dato! Hvis du ønsker at omlægge den, så afpublicér den først.', + 'publish_date_edit_success' => 'Episodens udgivelsesdato er blevet opdateret!', + 'unpublish_error' => 'Episoden er ikke publiceret.', + 'delete' => 'Slet', + 'go_to_page' => 'Gå til side', + 'create' => 'Tilføj en episode', + 'publication_status' => [ + 'published' => 'Udgivet', + 'with_podcast' => 'Udgivet', + 'scheduled' => 'Planlagt', + 'not_published' => 'Ikke offentliggjort', + ], + 'with_podcast_hint' => 'Skal offentliggøres samtidig med podcasten', + 'list' => [ + 'search' => [ + 'placeholder' => 'Søg efter en episode', + 'clear' => 'Ryd søgning', + 'submit' => 'Søg', + ], + 'number_of_episodes' => '{numberOfEpisodes, plural, + one {# episode} + other {# episoder} + }', + 'episode' => 'Episode', + 'visibility' => 'Synlighed', + 'downloads' => 'Downloads', + 'comments' => 'Kommentarer', + 'actions' => 'Handlinger', + ], + 'messages' => [ + 'createSuccess' => 'Episoden er blevet oprettet!', + 'editSuccess' => 'Episoden er blevet opdateret!', + 'publishSuccess' => '{publication_status, select, + published {Episode udgivet!} + scheduled {Episodeudgivelse planlagt!} + with_podcast {Denne episode vil blive offentliggjort samtidig med podcasten.} + other {Denne episode er ikke offentliggjort.} + }', + 'publishCancelSuccess' => 'Udgivelsen blev annulleret!', + 'unpublishBeforeDeleteTip' => 'Du skal afpublicere episoden, før du sletter den.', + 'scheduleDateError' => 'Udgivelsesdato skal være sat!', + 'deletePublishedEpisodeError' => 'Afpublicér venligst episoden, før du sletter den.', + 'deleteSuccess' => 'Episode slettet succesfuldt!', + 'deleteError' => 'Failed to delete episode {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deleteFileError' => 'Failed to delete {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + } file {file_key}. You may manually remove it from your disk.', + 'sameSlugError' => 'An episode with the chosen slug already exists.', + ], + 'form' => [ + 'file_size_error' => + 'Your file size is too big! Max size is {0}. Increase the `memory_limit`, `upload_max_filesize` and `post_max_size` values in your php configuration file then restart your web server to upload your file.', + 'audio_file' => 'Audio file', + 'audio_file_hint' => 'Choose an .mp3 or .m4a audio file.', + 'info_section_title' => 'Episode info', + 'cover' => 'Episode cover', + 'cover_hint' => + 'If you do not set a cover, the podcast cover will be used instead.', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'title' => 'Title', + 'title_hint' => + 'Should contain a clear and concise episode name. Do not specify the episode or season numbers here.', + 'permalink' => 'Permalink', + 'season_number' => 'Season', + 'episode_number' => 'Episode', + 'type' => [ + 'label' => 'Type', + 'full' => 'Full', + 'full_hint' => 'Complete content (the episode)', + 'trailer' => 'Trailer', + 'trailer_hint' => 'Short, promotional piece of content that represents a preview of the current show', + 'bonus' => 'Bonus', + 'bonus_hint' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show', + ], + 'premium_title' => 'Premium', + 'premium' => 'Episode must be accessible to premium subscribers only', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does the episode contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'show_notes_section_title' => 'Show notes', + 'show_notes_section_subtitle' => + 'Up to 4000 characters, be clear and concise. Show notes help potential listeners in finding the episode.', + 'description' => 'Description', + 'description_footer' => 'Description footer', + 'description_footer_hint' => + 'This text is added at the end of each episode description, it is a good place to input your social links for example.', + 'additional_files_section_title' => 'Additional files', + 'additional_files_section_subtitle' => + 'These files may be used by other platforms to provide better experience to your audience. See the {podcastNamespaceLink} for more information.', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this episode about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real or fictional location', + 'transcript' => 'Transcript (subtitles / closed captions)', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', + 'transcript_download' => 'Download transcript', + 'transcript_file' => 'Transcript file (.srt or .vtt)', + 'transcript_remote_url' => 'Remote url for transcript', + 'transcript_file_delete' => 'Delete transcript file', + 'chapters' => 'Chapters', + 'chapters_hint' => 'File must be in JSON Chapters format.', + 'chapters_download' => 'Download chapters', + 'chapters_file' => 'Chapters file', + 'chapters_remote_url' => 'Remote url for chapters file', + 'chapters_file_delete' => 'Delete chapters file', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the episode', + 'custom_rss_hint' => 'This will be injected within the ❬item❭ tag.', + 'block' => 'Episode should be hidden from public catalogues', + 'block_hint' => + 'The episode show or hide status: toggling this on prevents the episode from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'submit_create' => 'Create episode', + 'submit_edit' => 'Save episode', + ], + 'publish_form' => [ + 'back_to_episode_dashboard' => 'Back to episode dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your episode. The message will be broadcasted to all your followers in the fediverse and be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + 'with_podcast' => 'Publish alongside podcast', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_clear' => 'Clear publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the episode release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit' => 'Publish', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your episode.', + 'message_warning_submit' => 'Publish anyways', + ], + 'publish_date_edit_form' => [ + 'new_publication_date' => 'New publication date', + 'new_publication_date_hint' => 'Must be set to a past date.', + 'submit' => 'Edit publication date', + ], + 'unpublish_form' => [ + 'disclaimer' => + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", + 'understand' => 'I understand, I want to unpublish the episode', + 'submit' => 'Unpublish', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.", + 'understand' => 'I understand, I want to delete the episode', + 'submit' => 'Delete', + ], + 'embed' => [ + 'title' => 'Embeddable player', + 'label' => + 'Pick a theme color, copy the embeddable player to clipboard, then paste it on your website.', + 'clipboard_iframe' => 'Copy embeddable player to clipboard', + 'clipboard_url' => 'Copy address to clipboard', + 'dark' => 'Dark', + 'dark-transparent' => 'Dark transparent', + 'light' => 'Light', + 'light-transparent' => 'Light transparent', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], +]; diff --git a/modules/Admin/Language/da/EpisodeNavigation.php b/modules/Admin/Language/da/EpisodeNavigation.php new file mode 100644 index 00000000..1406e301 --- /dev/null +++ b/modules/Admin/Language/da/EpisodeNavigation.php @@ -0,0 +1,23 @@ + 'View episode page', + 'dashboard' => 'Episode dashboard', + 'episode-view' => 'Home', + 'episode-edit' => 'Edit episode', + 'episode-persons-manage' => 'Manage persons', + 'embed-add' => 'Embeddable player', + 'clips' => 'Clips', + 'video-clips-list' => 'Video clips', + 'video-clips-create' => 'New video clip', + 'soundbites-list' => 'Soundbites', + 'soundbites-create' => 'New soundbite', +]; diff --git a/modules/Admin/Language/da/Fediverse.php b/modules/Admin/Language/da/Fediverse.php new file mode 100644 index 00000000..0e4ca66d --- /dev/null +++ b/modules/Admin/Language/da/Fediverse.php @@ -0,0 +1,32 @@ + [ + 'actorNotFound' => 'The account could not be found!', + 'blockActorSuccess' => '{actor} has been blocked!', + 'unblockActorSuccess' => 'Actor has been unblocked!', + 'blockDomainSuccess' => '{domain} has been blocked!', + 'unblockDomainSuccess' => '{domain} has been unblocked!', + ], + 'blocked_actors' => 'Blocked accounts', + 'blocked_domains' => 'Blocked domains', + 'block_lists_form' => [ + 'handle' => 'Account handle', + 'handle_hint' => 'Input @username@domain account.', + 'domain' => 'Domain name', + 'submit' => 'Block!', + ], + 'list' => [ + 'actor' => 'Account', + 'domain' => 'Domain name', + 'unblock' => 'Unblock', + ], +]; diff --git a/app/Entities/Media/Chapters.php b/modules/Admin/Language/da/Home.php similarity index 51% rename from app/Entities/Media/Chapters.php rename to modules/Admin/Language/da/Home.php index 227cb5af..3ff4c04d 100644 --- a/app/Entities/Media/Chapters.php +++ b/modules/Admin/Language/da/Home.php @@ -3,14 +3,12 @@ declare(strict_types=1); /** - * @copyright 2021 Ad Aures + * @copyright 2020 Ad Aures * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @link https://castopod.org/ */ -namespace App\Entities\Media; - -class Chapters extends BaseMedia -{ - protected string $type = 'chapters'; -} +return [ + 'all_podcasts' => 'All podcasts', + 'no_podcast' => 'No podcast found', +]; diff --git a/modules/Admin/Language/da/Install.php b/modules/Admin/Language/da/Install.php new file mode 100644 index 00000000..36e373a2 --- /dev/null +++ b/modules/Admin/Language/da/Install.php @@ -0,0 +1,61 @@ + 'Manual configuration', + 'manual_config_subtitle' => + 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'form' => [ + 'instance_config' => 'Instance configuration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', + 'db_hostname' => 'Database hostname', + 'db_name' => 'Database name', + 'db_username' => 'Database username', + 'db_password' => 'Database password', + 'db_prefix' => 'Database prefix', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Cache handler', + 'cacheHandlerOptions' => [ + 'file' => 'File', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Next', + 'submit' => 'Finish install', + 'create_superadmin' => 'Create your superadmin account', + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Your superadmin account has been created successfully. Login to start podcasting!', + 'databaseConnectError' => + 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'writeError' => + "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + ], +]; diff --git a/modules/Admin/Language/da/Navigation.php b/modules/Admin/Language/da/Navigation.php new file mode 100644 index 00000000..f3ffb129 --- /dev/null +++ b/modules/Admin/Language/da/Navigation.php @@ -0,0 +1,44 @@ + 'Toggle sidebar', + 'go_to_website' => 'Go to website', + 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', + 'dashboard' => 'Dashboard', + 'admin' => 'Home', + 'podcasts' => 'Podcasts', + 'podcast-list' => 'All podcasts', + 'podcast-create' => 'New podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', + 'persons' => 'Persons', + 'person-list' => 'All persons', + 'person-create' => 'New person', + 'fediverse' => 'Fediverse', + 'fediverse-blocked-actors' => 'Blocked accounts', + 'fediverse-blocked-domains' => 'Blocked domains', + 'users' => 'Users', + 'user-list' => 'All users', + 'user-create' => 'New user', + 'pages' => 'Pages', + 'page-list' => 'All pages', + 'page-create' => 'New Page', + 'settings' => 'Settings', + 'settings-general' => 'General', + 'settings-theme' => 'Theme', + 'admin-about' => 'About', + 'account' => [ + 'my-account' => 'My account', + 'change-password' => 'Change password', + 'logout' => 'Logout', + ], +]; diff --git a/modules/Admin/Language/nn-NO/Notifications.php b/modules/Admin/Language/da/Notifications.php similarity index 100% rename from modules/Admin/Language/nn-NO/Notifications.php rename to modules/Admin/Language/da/Notifications.php diff --git a/modules/Admin/Language/da/Page.php b/modules/Admin/Language/da/Page.php new file mode 100644 index 00000000..b6f49de5 --- /dev/null +++ b/modules/Admin/Language/da/Page.php @@ -0,0 +1,30 @@ + 'Back to home', + 'page' => 'Page', + 'all_pages' => 'All pages', + 'create' => 'New page', + 'go_to_page' => 'Go to page', + 'edit' => 'Edit page', + 'delete' => 'Delete page', + 'form' => [ + 'title' => 'Title', + 'permalink' => 'Permalink', + 'content' => 'Content', + 'submit_create' => 'Create page', + 'submit_edit' => 'Save', + ], + 'messages' => [ + 'createSuccess' => 'The page “{pageTitle}” was created successfully!', + 'editSuccess' => 'The page was successfully updated!', + ], +]; diff --git a/modules/Admin/Language/da/Pager.php b/modules/Admin/Language/da/Pager.php new file mode 100644 index 00000000..e25ee638 --- /dev/null +++ b/modules/Admin/Language/da/Pager.php @@ -0,0 +1,21 @@ + 'Page navigation', + 'first' => 'First', + 'previous' => 'Previous', + 'next' => 'Next', + 'last' => 'Last', + 'older' => 'Older', + 'newer' => 'Newer', + 'invalidTemplate' => '{0} is not a valid Pager template.', + 'invalidPaginationGroup' => '{0} is not a valid Pagination group.', +]; diff --git a/modules/Admin/Language/da/Person.php b/modules/Admin/Language/da/Person.php new file mode 100644 index 00000000..a652be9f --- /dev/null +++ b/modules/Admin/Language/da/Person.php @@ -0,0 +1,65 @@ + 'Persons', + 'all_persons' => 'All persons', + 'no_person' => 'Nobody found!', + 'create' => 'Create a person', + 'view' => 'View person', + 'edit' => 'Edit person', + 'delete' => 'Delete person', + 'messages' => [ + 'createSuccess' => 'Person has been successfully created!', + 'editSuccess' => 'Person has been successfully updated!', + 'deleteSuccess' => 'Person has been removed!', + ], + 'form' => [ + 'avatar' => 'Avatar', + 'avatar_size_hint' => + 'Avatar must be squared and at least 400px wide and tall.', + 'full_name' => 'Full name', + 'full_name_hint' => 'This is the full name or alias of the person.', + 'unique_name' => 'Unique name', + 'unique_name_hint' => 'Used for URLs', + 'information_url' => 'Information URL', + 'information_url_hint' => + 'Url to a relevant resource of information about the person, such as a homepage or third-party profile platform.', + 'submit_create' => 'Create person', + 'submit_edit' => 'Save person', + ], + 'podcast_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this podcast', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'episode_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this episode', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'credits' => 'Credits', +]; diff --git a/modules/Admin/Language/da/Platforms.php b/modules/Admin/Language/da/Platforms.php new file mode 100644 index 00000000..e161181c --- /dev/null +++ b/modules/Admin/Language/da/Platforms.php @@ -0,0 +1,43 @@ + [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', + 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', + 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', + 'visible' => 'Display in podcast homepage?', + 'on_embed' => 'Display on embeddable player?', + 'remove' => 'Remove {platformName}', + 'submit' => 'Save', + 'messages' => [ + 'updateSuccess' => 'Platform links have been successfully updated!', + 'removeLinkSuccess' => 'The platform link has been removed.', + 'removeLinkError' => + 'The platform link could not be removed. Try again.', + ], + 'description' => [ + 'podcasting' => 'The podcast ID on this platform', + 'social' => 'The podcast account ID on this platform', + 'funding' => 'Call to action message', + ], +]; diff --git a/modules/Admin/Language/da/Podcast.php b/modules/Admin/Language/da/Podcast.php new file mode 100644 index 00000000..ff0daebc --- /dev/null +++ b/modules/Admin/Language/da/Podcast.php @@ -0,0 +1,330 @@ + 'All podcasts', + 'no_podcast' => 'No podcast found!', + 'create' => 'Create podcast', + 'import' => 'Import podcast', + 'all_imports' => 'Podcast imports', + 'new_episode' => 'New Episode', + 'view' => 'View podcast', + 'edit' => 'Edit podcast', + 'publish' => 'Publish podcast', + 'publish_edit' => 'Edit publication', + 'delete' => 'Delete podcast', + 'see_episodes' => 'See episodes', + 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', + 'go_to_page' => 'Go to page', + 'latest_episodes' => 'Latest episodes', + 'see_all_episodes' => 'See all episodes', + 'draft' => 'Draft', + 'messages' => [ + 'createSuccess' => 'Podcast successfully created!', + 'editSuccess' => 'Podcast has been successfully updated!', + 'importSuccess' => 'Podcast has been successfully imported!', + 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', + 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, + cover {cover} + banner {banner} + other {media} + }.', + 'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.', + 'podcastFeedUpdateSuccess' => 'Successful update: {number_of_new_episodes, plural, + one {# episode was} + other {# episodes were} + } added to the podcast!', + 'podcastFeedUpToDate' => 'Podcast is already up to date.', + 'publishError' => 'This podcast is either already published or scheduled for publication.', + 'publishEditError' => 'This podcast is not scheduled for publication.', + 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', + 'scheduleDateError' => 'Schedule date must be set!', + ], + 'form' => [ + 'identity_section_title' => 'Podcast identity', + 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + + 'cover' => 'Podcast cover', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'banner' => 'Podcast banner', + 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', + 'banner_delete' => 'Delete podcast banner', + 'title' => 'Title', + 'handle' => 'Handle', + 'handle_hint' => + 'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.', + 'type' => [ + 'label' => 'Type', + 'episodic' => 'Episodic', + 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', + 'serial' => 'Serial', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + ], + 'description' => 'Description', + 'classification_section_title' => 'Classification', + 'classification_section_subtitle' => + 'These fields will impact your audience and competition.', + 'language' => 'Language', + 'category' => 'Category', + 'category_placeholder' => 'Select a category…', + 'other_categories' => 'Other categories', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does it contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'author_section_title' => 'Author', + 'author_section_subtitle' => 'Who is managing the podcast?', + 'owner_name' => 'Owner name', + 'owner_name_hint' => + 'For administrative use only. Visible in the public RSS feed.', + 'owner_email' => 'Owner email', + 'owner_email_hint' => + 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', + 'publisher' => 'Publisher', + 'publisher_hint' => + 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', + 'copyright' => 'Copyright', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this podcast about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real place or fictional', + 'monetization_section_title' => 'Monetization', + 'monetization_section_subtitle' => + 'Earn money thanks to your audience.', + 'premium' => 'Premium', + 'premium_by_default' => 'Episodes must be set as premium by default', + 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', + 'payment_pointer' => 'Payment Pointer for Web Monetization', + 'payment_pointer_hint' => + 'This is your where you will receive money thanks to Web Monetization', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the podcast', + 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', + 'new_feed_url' => 'New feed URL', + 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', + 'old_feed_url' => 'Old feed URL', + 'partnership' => 'Partnership', + 'partner_id' => 'ID', + 'partner_link_url' => 'Link URL', + 'partner_image_url' => 'Image URL', + 'partner_id_hint' => 'Your own partner ID', + 'partner_link_url_hint' => 'The generic partner link address', + 'partner_image_url_hint' => 'The generic partner image address', + 'block' => 'Podcast should be hidden from public catalogues', + 'block_hint' => + 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'complete' => 'Podcast will not be having new episodes', + 'lock' => 'Prevent podcast from being copied', + 'lock_hint' => + 'The purpose is to tell other podcast platforms whether they are allowed to import this feed. A value of yes means that any attempt to import this feed into a new platform should be rejected.', + 'submit_create' => 'Create podcast', + 'submit_edit' => 'Save podcast', + ], + 'category_options' => [ + 'uncategorized' => 'uncategorized', + 'arts' => 'Arts', + 'business' => 'Business', + 'comedy' => 'Comedy', + 'education' => 'Education', + 'fiction' => 'Fiction', + 'government' => 'Government', + 'health_and_fitness' => 'Health & Fitness', + 'history' => 'History', + 'kids_and_family' => 'Kids & Family', + 'leisure' => 'Leisure', + 'music' => 'Music', + 'news' => 'News', + 'religion_and_spirituality' => 'Religion & Spirituality', + 'science' => 'Science', + 'society_and_culture' => 'Society & Culture', + 'sports' => 'Sports', + 'technology' => 'Technology', + 'true_crime' => 'True Crime', + 'tv_and_film' => 'TV & Film', + 'books' => 'Books', + 'design' => 'Design', + 'fashion_and_beauty' => 'Fashion & Beauty', + 'food' => 'Food', + 'performing_arts' => 'Performing Arts', + 'visual_arts' => 'Visual Arts', + 'careers' => 'Careers', + 'entrepreneurship' => 'Entrepreneurship', + 'investing' => 'Investing', + 'management' => 'Management', + 'marketing' => 'Marketing', + 'non_profit' => 'Non-Profit', + 'comedy_interviews' => 'Comedy Interviews', + 'improv' => 'Improv', + 'stand_up' => 'Stand-Up', + 'courses' => 'Courses', + 'how_to' => 'How To', + 'language_learning' => 'Language Learning', + 'self_improvement' => 'Self-Improvement', + 'comedy_fiction' => 'Comedy Fiction', + 'drama' => 'Drama', + 'science_fiction' => 'Science Fiction', + 'alternative_health' => 'Alternative Health', + 'fitness' => 'Fitness', + 'medicine' => 'Medicine', + 'mental_health' => 'Mental Health', + 'nutrition' => 'Nutrition', + 'sexuality' => 'Sexuality', + 'education_for_kids' => 'Education for Kids', + 'parenting' => 'Parenting', + 'pets_and_animals' => 'Pets & Animals', + 'stories_for_kids' => 'Stories for Kids', + 'animation_and_manga' => 'Animation & Manga', + 'automotive' => 'Automotive', + 'aviation' => 'Aviation', + 'crafts' => 'Crafts', + 'games' => 'Games', + 'hobbies' => 'Hobbies', + 'home_and_garden' => 'Home & Garden', + 'video_games' => 'Video Games', + 'music_commentary' => 'Music Commentary', + 'music_history' => 'Music History', + 'music_interviews' => 'Music Interviews', + 'business_news' => 'Business News', + 'daily_news' => 'Daily News', + 'entertainment_news' => 'Entertainment News', + 'news_commentary' => 'News Commentary', + 'politics' => 'Politics', + 'sports_news' => 'Sports News', + 'tech_news' => 'Tech News', + 'buddhism' => 'Buddhism', + 'christianity' => 'Christianity', + 'hinduism' => 'Hinduism', + 'islam' => 'Islam', + 'judaism' => 'Judaism', + 'religion' => 'Religion', + 'spirituality' => 'Spirituality', + 'astronomy' => 'Astronomy', + 'chemistry' => 'Chemistry', + 'earth_sciences' => 'Earth Sciences', + 'life_sciences' => 'Life Sciences', + 'mathematics' => 'Mathematics', + 'natural_sciences' => 'Natural Sciences', + 'nature' => 'Nature', + 'physics' => 'Physics', + 'social_sciences' => 'Social Sciences', + 'documentary' => 'Documentary', + 'personal_journals' => 'Personal Journals', + 'philosophy' => 'Philosophy', + 'places_and_travel' => 'Places & Travel', + 'relationships' => 'Relationships', + 'baseball' => 'Baseball', + 'basketball' => 'Basketball', + 'cricket' => 'Cricket', + 'fantasy_sports' => 'Fantasy Sports', + 'football' => 'Football', + 'golf' => 'Golf', + 'hockey' => 'Hockey', + 'rugby' => 'Rugby', + 'running' => 'Running', + 'soccer' => 'Soccer', + 'swimming' => 'Swimming', + 'tennis' => 'Tennis', + 'volleyball' => 'Volleyball', + 'wilderness' => 'Wilderness', + 'wrestling' => 'Wrestling', + 'after_shows' => 'After Shows', + 'film_history' => 'Film History', + 'film_interviews' => 'Film Interviews', + 'film_reviews' => 'Film Reviews', + 'tv_reviews' => 'TV Reviews', + ], + 'publish_form' => [ + 'back_to_podcast_dashboard' => 'Back to podcast dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'submit' => 'Publish', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', + 'message_warning_submit' => 'Publish anyway', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'not_published' => 'This podcast is not yet published.', + 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", + 'understand' => 'I understand, I want the podcast to be permanently deleted', + 'submit' => 'Delete', + ], + 'by' => 'By {publisher}', + 'season' => 'Season {seasonNumber}', + 'list_of_episodes_year' => '{year} episodes ({episodeCount})', + 'list_of_episodes_season' => + 'Season {seasonNumber} episodes ({episodeCount})', + 'no_episode' => 'No episode found!', + 'follow' => 'Follow', + 'followers' => '{numberOfFollowers, plural, + one {# follower} + other {# followers} + }', + 'posts' => '{numberOfPosts, plural, + one {# post} + other {# posts} + }', + 'activity' => 'Activity', + 'episodes' => 'Episodes', + 'sponsor' => 'Sponsor', + 'funding_links' => 'Funding links for {podcastTitle}', + 'find_on' => 'Find {podcastTitle} on', + 'listen_on' => 'Listen on', +]; diff --git a/modules/Admin/Language/da/PodcastNavigation.php b/modules/Admin/Language/da/PodcastNavigation.php new file mode 100644 index 00000000..bb777707 --- /dev/null +++ b/modules/Admin/Language/da/PodcastNavigation.php @@ -0,0 +1,42 @@ + 'Go to podcast page', + 'rss_feed' => 'RSS feed', + 'dashboard' => 'Podcast dashboard', + 'podcast-view' => 'Home', + 'podcast-edit' => 'Edit podcast', + 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', + 'episodes' => 'Episodes', + 'episode-list' => 'All episodes', + 'episode-create' => 'New episode', + 'analytics' => 'Analytics', + 'podcast-analytics' => 'Audience overview', + 'podcast-analytics-webpages' => 'Web pages visits', + 'podcast-analytics-locations' => 'Locations', + 'podcast-analytics-unique-listeners' => 'Unique listeners', + 'podcast-analytics-players' => 'Players', + 'podcast-analytics-listening-time' => 'Listening time', + 'podcast-analytics-time-periods' => 'Time periods', + 'monetization' => 'Monetization', + 'subscription-list' => 'All subscriptions', + 'subscription-create' => 'Add subscription', + 'contributors' => 'Contributors', + 'contributor-list' => 'All contributors', + 'contributor-add' => 'Add contributor', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', + 'platforms-social' => 'Social networks', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', +]; diff --git a/modules/Admin/Language/da/Settings.php b/modules/Admin/Language/da/Settings.php new file mode 100644 index 00000000..4a70dcba --- /dev/null +++ b/modules/Admin/Language/da/Settings.php @@ -0,0 +1,58 @@ + 'General settings', + 'instance' => [ + 'title' => 'Instance', + 'site_icon' => 'Site icon', + 'site_icon_delete' => 'Delete site icon', + 'site_icon_hint' => 'Site icons are what you see on your browser tabs, bookmarks bar, and when you add a website as a shortcut on mobile devices.', + 'site_icon_helper' => 'Icon must be squared and at least 512px wide and tall.', + 'site_name' => 'Site name', + 'site_description' => 'Site description', + 'submit' => 'Save', + 'editSuccess' => 'Instance has been updated successfully!', + 'deleteIconSuccess' => 'Site icon has been remove successfully!', + ], + 'images' => [ + 'title' => 'Images', + 'subtitle' => 'Here you can regenerate all images based on the originals that were uploaded. To be used if you find that some images are missing. This task may take a while.', + 'regenerate' => 'Regenerate images', + 'regenerationSuccess' => 'All images have been regenerated successfully!', + ], + 'housekeeping' => [ + 'title' => 'Housekeeping', + 'subtitle' => 'Runs various housekeeping tasks. Use this feature if you ever encounter issues with media files or data integrity. These tasks may take a while.', + 'reset_counts' => 'Reset counts', + 'reset_counts_helper' => 'This option will recalculate and reset all data counts (number of followers, posts, comments, …).', + 'rewrite_media' => 'Rewrite media metadata', + 'rewrite_media_helper' => 'This option will delete all superfluous media files and recreate them (images, audio files, transcripts, chapters, …)', + 'rename_episodes_files' => 'Rename episode audio files', + 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', + 'clear_cache' => 'Clear all cache', + 'clear_cache_helper' => 'This option will flush redis cache or writable/cache files.', + 'run' => 'Run housekeeping', + 'runSuccess' => 'Housekeeping has been run successfully!', + ], + 'theme' => [ + 'title' => 'Theme', + 'accent_section_title' => 'Accent color', + 'accent_section_subtitle' => 'Choose the color to determine the look and feel of all public pages.', + 'pine' => 'Pine', + 'crimson' => 'Crimson', + 'amber' => 'Amber', + 'lake' => 'Lake', + 'jacaranda' => 'Jacaranda', + 'onyx' => 'Onyx', + 'submit' => 'Save', + 'setInstanceThemeSuccess' => 'Theme has been updated successfully!', + ], +]; diff --git a/modules/Admin/Language/da/Soundbite.php b/modules/Admin/Language/da/Soundbite.php new file mode 100644 index 00000000..a3f828fe --- /dev/null +++ b/modules/Admin/Language/da/Soundbite.php @@ -0,0 +1,31 @@ + [ + 'title' => 'Soundbites', + 'soundbite' => 'Soundbite', + ], + 'messages' => [ + 'createSuccess' => 'Soundbite has been successfully created!', + 'deleteSuccess' => 'Soundbite has been successfully removed!', + ], + 'form' => [ + 'title' => 'New soundbite', + 'soundbite_title' => 'Soundbite title', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'submit' => 'Create soundbite', + ], + 'play' => 'Play soundbite', + 'stop' => 'Stop soundbite', + 'create' => 'New soundbite', + 'delete' => 'Delete soundbite', +]; diff --git a/modules/Admin/Language/da/Validation.php b/modules/Admin/Language/da/Validation.php new file mode 100644 index 00000000..f76c3163 --- /dev/null +++ b/modules/Admin/Language/da/Validation.php @@ -0,0 +1,17 @@ + + '{field} is either not an image, or it is not wide or tall enough.', + 'is_image_ratio' => + '{field} is either not an image or not of the right ratio.', + 'is_json' => '{field} contains invalid JSON.', +]; diff --git a/modules/Admin/Language/da/VideoClip.php b/modules/Admin/Language/da/VideoClip.php new file mode 100644 index 00000000..638de697 --- /dev/null +++ b/modules/Admin/Language/da/VideoClip.php @@ -0,0 +1,72 @@ + [ + 'title' => 'Video clips', + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Clip is waiting to be processed.', + 'pending' => 'pending', + 'pending_hint' => 'Clip will be generated shortly.', + 'running' => 'running', + 'running_hint' => 'Clip is being generated.', + 'failed' => 'failed', + 'failed_hint' => 'Clip could not be generated: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Clip was generated successfully!', + ], + 'clip' => 'Clip', + 'duration' => 'Job duration', + ], + 'title' => 'Video clip: {videoClipLabel}', + 'download_clip' => 'Download clip', + 'create' => 'New video clip', + 'go_to_page' => 'Go to clip page', + 'retry' => 'Retry clip generation', + 'delete' => 'Delete clip', + 'logs' => 'Job logs', + 'messages' => [ + 'alreadyExistingError' => 'The video clip you are trying to create already exists!', + 'addToQueueSuccess' => 'Video clip has been added to queue, awaiting to be created!', + 'deleteSuccess' => 'Video clip has been successfully removed!', + ], + 'format' => [ + 'landscape' => 'Landscape', + 'portrait' => 'Portrait', + 'squared' => 'Squared', + ], + 'form' => [ + 'title' => 'New video clip', + 'params_section_title' => 'Video clip parameters', + 'clip_title' => 'Clip title', + 'format' => [ + 'label' => 'Choose a format', + 'landscape_hint' => 'With a 16:9 ratio, landscape videos are great for PeerTube, Youtube and Vimeo.', + 'portrait_hint' => 'With a 9:16 ratio, portrait videos are great for TikTok, Youtube shorts and Instagram stories.', + 'squared_hint' => 'With a 1:1 ratio, squared videos are great for Mastodon, Facebook, Twitter and LinkedIn.', + ], + 'theme' => 'Select a theme', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'trim_start' => 'Trim start', + 'trim_end' => 'Trim end', + 'submit' => 'Create video clip', + ], + 'requirements' => [ + 'title' => 'Missing requirements', + 'missing' => 'You have missing requirements. Make sure to add all the required items to be allowed creating a video for this episode!', + 'ffmpeg' => 'FFmpeg', + 'gd' => 'Graphics Draw (GD)', + 'freetype' => 'Freetype library for GD', + 'transcript' => 'Transcript file (.srt)', + ], +]; diff --git a/modules/Admin/Language/de/AboutCastopod.php b/modules/Admin/Language/de/AboutCastopod.php new file mode 100644 index 00000000..7e8780ad --- /dev/null +++ b/modules/Admin/Language/de/AboutCastopod.php @@ -0,0 +1,22 @@ + 'Über Castopod', + 'host_name' => 'Hostname', + 'version' => 'Castopod Version', + 'php_version' => 'PHP Version', + 'os' => 'Betriebssystem', + 'languages' => 'Sprachen', + 'update_database' => 'Datenbank aktualisieren', + 'messages' => [ + 'databaseUpdateSuccess' => 'Die Datenbank ist aktuell!', + ], +]; diff --git a/modules/Admin/Language/de/Breadcrumb.php b/modules/Admin/Language/de/Breadcrumb.php index 2bf0fc70..986f03dd 100644 --- a/modules/Admin/Language/de/Breadcrumb.php +++ b/modules/Admin/Language/de/Breadcrumb.php @@ -14,39 +14,44 @@ return [ ->gateway => 'Startseite', 'podcasts' => 'Podcasts', 'episodes' => 'Folgen', - 'subscriptions' => 'subscriptions', - 'contributors' => 'Administratoren', + 'subscriptions' => 'Abonnements', + 'contributors' => 'Mitwirkende', 'pages' => 'Seiten', 'settings' => 'Einstellungen', 'theme' => 'Erscheinungsbild', + 'about' => 'Über', 'add' => 'hinzufügen', 'new' => 'neu', 'edit' => 'bearbeiten', 'persons' => 'Mitwirkende', 'publish' => 'veröffentlichen', 'publish-edit' => 'Veröffentlichung bearbeiten', - 'publish-date-edit' => 'edit publication date', - 'unpublish' => 'Veröffentlichung aufheben', + 'publish-date-edit' => 'Veröffentlichungsdatum bearbeiten', + 'unpublish' => 'zurückziehen', 'delete' => 'löschen', + 'remove' => 'Entfernen', 'fediverse' => 'Fediversum', - 'block-lists' => 'Sperrlisten', + 'blocked-actors' => 'blockierte Konten', + 'blocked-domains' => 'Blockierte Domains', 'users' => 'Benutzer', 'my-account' => 'Mein Konto', 'change-password' => 'Passwort ändern', - 'import' => 'Feed-Import', + 'imports' => 'Importe', + 'sync-feeds' => 'Feeds synchronisieren', 'platforms' => 'Plattformen', 'social' => 'soziale Netzwerke', 'funding' => 'Finanzierung', - 'analytics' => 'Analysen', + 'monetization-other' => 'sonstige Monetarisierung', + 'analytics' => 'Statistiken', 'locations' => 'Orte', 'webpages' => 'Webseiten', - 'unique-listeners' => 'einzigartige Zuhörer', + 'unique-listeners' => 'eindeutige Zuhörer', 'players' => 'Podcast-Player', 'listening-time' => 'Hörzeit', 'time-periods' => 'Zeiträume', 'soundbites' => 'Tonschnipsel', 'video-clips' => 'Videoclips', 'embed' => 'einbettbarer Spieler', - 'notifications' => 'notifications', - 'suspend' => 'suspend', + 'notifications' => 'Benachrichtigungen', + 'suspend' => 'Unterbrechen', ]; diff --git a/modules/Admin/Language/de/Charts.php b/modules/Admin/Language/de/Charts.php index c96b1dcb..89cea21b 100644 --- a/modules/Admin/Language/de/Charts.php +++ b/modules/Admin/Language/de/Charts.php @@ -15,8 +15,8 @@ return [ 'by_device_weekly' => 'Downloads von Folgen nach Gerät (für die vergangene Woche)', 'by_os_weekly' => 'Downloads von Folgen nach Betriebssystem (für die vergangene Woche)', 'podcast_by_region' => 'Downloads von Folgen nach Region (für die vergangene Woche)', - 'unique_daily_listeners' => 'Tägliche einzigartige Zuhörer', - 'unique_monthly_listeners' => 'Monatliche einzigartige Zuhörer', + 'unique_daily_listeners' => 'Tägliche eindeutige Zuhörer', + 'unique_monthly_listeners' => 'Monatliche eindeutige Zuhörer', 'by_browser' => 'Nutzung der Webseiten nach Browser (für die vergangene Woche)', 'podcast_by_day' => 'Tägliche Downloads von Folgen', 'podcast_by_month' => 'Monatliche Downloads von Folgen', @@ -35,6 +35,7 @@ return [ 'by_weekday' => 'Nach Wochentag (für die letzten 60 Tage)', 'by_hour' => 'Nach Tageszeit (für die letzten 60 Tage)', 'podcast_by_bandwidth' => 'Täglich genutzte Bandbreite (in MB)', - 'total_storage_by_month' => 'Monthly storage (in MB)', - 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_storage_by_month' => 'Monatlicher Speicher (in MB)', + 'total_bandwidth_by_month' => 'Monatlich genutzte Bandbreite (in MB)', + 'total_bandwidth_by_month_limit' => 'Begrenzt auf {totalBandwidth} pro Monat', ]; diff --git a/modules/Admin/Language/de/Common.php b/modules/Admin/Language/de/Common.php index f9b6a99c..cc668a33 100644 --- a/modules/Admin/Language/de/Common.php +++ b/modules/Admin/Language/de/Common.php @@ -21,7 +21,7 @@ return [ 'copied' => 'Kopiert!', 'home' => 'Startseite', 'explicit' => 'Anstößig', - 'powered_by' => 'Betrieben durch {castopod}', + 'powered_by' => 'Betrieben mit {castopod}', 'actions' => 'Aktionen', 'pageInfo' => 'Seite {currentPage} von {pageCount}', 'go_back' => 'Zurück', @@ -40,12 +40,13 @@ return [ ], 'upload_file' => 'Eine Datei hochladen', 'remote_url' => 'Externe URL', + 'save' => 'Speichern', ], 'play_episode_button' => [ 'play' => 'Abspielen', 'playing' => 'Spielt', ], 'size_limit' => 'Größenlimit: {0}.', - 'choose_interact' => 'Choose how to interact', - 'view' => 'View', + 'choose_interact' => 'Wählen Sie, wie Sie interagieren möchten', + 'view' => 'Ansicht', ]; diff --git a/modules/Admin/Language/de/Dashboard.php b/modules/Admin/Language/de/Dashboard.php index 881073fd..2b539a71 100644 --- a/modules/Admin/Language/de/Dashboard.php +++ b/modules/Admin/Language/de/Dashboard.php @@ -9,20 +9,20 @@ declare(strict_types=1); */ return [ - 'home' => 'Admin dashboard', - 'welcome_message' => 'Welcome to the admin area!', + 'home' => 'Admin-Dashboard', + 'welcome_message' => 'Willkommen im Administrationsbereich!', 'podcasts' => [ 'title' => 'Podcasts', - 'not_found' => 'No published podcast', - 'last_published' => 'Last published on {lastPublicationDate}', + 'not_found' => 'Kein veröffentlichter Podcast', + 'last_published' => 'Zuletzt veröffentlicht am {lastPublicationDate}', ], 'episodes' => [ - 'title' => 'Episodes', - 'not_found' => 'No published episode', - 'last_published' => 'Last published on {lastPublicationDate}', + 'title' => 'Folgen', + 'not_found' => 'Keine veröffentlichte Episode', + 'last_published' => 'Zuletzt veröffentlicht am {lastPublicationDate}', ], 'storage' => [ - 'title' => 'Storage', - 'subtitle' => '{totalUploaded} out of {totalStorage}', + 'title' => 'Speicher', + 'subtitle' => '{totalUploaded} von {totalStorage}', ], ]; diff --git a/modules/Admin/Language/de/Episode.php b/modules/Admin/Language/de/Episode.php index d20fd716..15009e31 100644 --- a/modules/Admin/Language/de/Episode.php +++ b/modules/Admin/Language/de/Episode.php @@ -13,7 +13,7 @@ return [ 'season_abbr' => 'S{seasonNumber}', 'number' => 'Folge {episodeNumber}', 'number_abbr' => 'F. {episodeNumber}', - 'season_episode' => 'Staffel {seasonNumber} episode {episodeNumber}', + 'season_episode' => 'Staffel {seasonNumber} Folge {episodeNumber}', 'season_episode_abbr' => 'S{seasonNumber}F{episodeNumber}', 'number_of_comments' => '{numberOfComments, plural, one {# Kommentar} @@ -22,27 +22,28 @@ return [ 'all_podcast_episodes' => 'Alle Podcast-Episoden', 'back_to_podcast' => 'Zurück zum Podcast', 'edit' => 'Bearbeiten', + 'preview' => 'Vorschau', 'publish' => 'Veröffentllichen', 'publish_edit' => 'Veröffentlichung bearbeiten', - 'publish_date_edit' => 'Edit publication date', + 'publish_date_edit' => 'Veröffentlichungsdatum bearbeiten', 'unpublish' => 'Veröffentlichung zurücknehmen', 'publish_error' => 'Folge ist bereits veröffentlicht.', 'publish_edit_error' => 'Folge ist bereits veröffentlicht.', 'publish_cancel_error' => 'Folge ist bereits veröffentlicht.', - 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', - 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', - 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'publish_date_edit_error' => 'Die Folge wurde noch nicht veröffentlicht, Sie können das Veröffentlichungsdatum nicht bearbeiten.', + 'publish_date_edit_future_error' => 'Das Veröffentlichungsdatum der Folge kann nur auf ein vergangenes Datum gesetzt werden! Wenn Sie es neu planen möchten, heben Sie die Veröffentlichung zuerst auf.', + 'publish_date_edit_success' => 'Das Veröffentlichungsdatum der Folge wurde erfolgreich aktualisiert!', 'unpublish_error' => 'Folge ist nicht veröffentlicht.', 'delete' => 'Löschen', 'go_to_page' => 'Gehe zu Seite', 'create' => 'Folge hinzufügen', 'publication_status' => [ 'published' => 'Veröffentlicht', - 'with_podcast' => 'Published', + 'with_podcast' => 'Veröffentlicht', 'scheduled' => 'Geplant', 'not_published' => 'Nicht veröffentlicht', ], - 'with_podcast_hint' => 'To be published at the same time as the podcast', + 'with_podcast_hint' => 'Wird zeitgleich mit dem Podcast veröffentlicht', 'list' => [ 'search' => [ 'placeholder' => 'Suche nach einer Episode', @@ -50,42 +51,43 @@ return [ 'submit' => 'Suche', ], 'number_of_episodes' => '{numberOfEpisodes, plural, - one {# episode} - other {# episodes} + one {# Folge} + other {# Folgen} }', 'episode' => 'Folge', - 'visibility' => 'Sichtweite', - 'comments' => 'Komemntar', + 'visibility' => 'Sichtbarkeit', + 'downloads' => 'Downloads', + 'comments' => 'Kommentar', 'actions' => 'Aktionen', ], 'messages' => [ 'createSuccess' => 'Folge wurde erfolgreich erstellt!', 'editSuccess' => 'Folge wurde erfolgreich aktualisiert!', 'publishSuccess' => '{publication_status, select, - published {Episode successfully published!} - scheduled {Episode publication successfully scheduled!} - with_podcast {This episode will be published at the same time as the podcast.} - other {This episode is not published.} + published {Folge erfolgreich veröffentlicht!} + scheduled {Veröffentlichung der Folge erfolgreich geplant!} + with_podcast {Diese Folge wird zeitgleich mit dem Podcast veröffentlicht.} + other {Diese Folge ist nicht veröffentlicht.} }', - 'publishCancelSuccess' => 'Veröffentlichung der Episode erfolgreich abgebrochen!', + 'publishCancelSuccess' => 'Veröffentlichung der Episode abgebrochen!', 'unpublishBeforeDeleteTip' => 'Du musst die Episode zurückziehen, bevor du sie löschst.', - 'scheduleDateError' => 'Schedule date must be set!', + 'scheduleDateError' => 'Veröffentlichungsdatum muss gesetzt sein!', 'deletePublishedEpisodeError' => 'Bitte ziehe die Episode zurück, bevor du sie löschst.', 'deleteSuccess' => 'Folge erfolgreich gelöscht!', - 'deleteError' => 'Failed to delete episode {type, select, - transcript {transcript} - chapters {chapters} - image {cover} - audio {audio} - other {media} - }.', - 'deleteFileError' => 'Failed to delete {type, select, - transcript {transcript} - chapters {chapters} - image {cover} - audio {audio} - other {media} - } file {file_path}. You may manually remove it from your disk.', + 'deleteError' => 'Fehler beim Löschen der {type, select, + transcript {Abschrift} + chapters {Kapitel} + image {Cover} + audio {Audio} + other {Medien} + }-Datei dieser Episode.', + 'deleteFileError' => 'Fehler beim Löschen der {type, select, + transcript {Abschrift} + chapters {Kapitel} + image {Cover} + audio {Audio} + other {Medien} + }-Datei {file_key}. Sie können es manuell von der Festplatte entfernen.', 'sameSlugError' => 'Eine Folge mit dem ausgewählten Slug existiert bereits.', ], 'form' => [ @@ -97,7 +99,7 @@ return [ 'cover' => 'Episoden-Cover', 'cover_hint' => 'Wenn Du kein Cover festlegst, wird stattdessen das Podcast-Cover verwendet.', - 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'cover_size_hint' => 'Das Cover muss quadratisch und mindestens 1400px breit und hoch sein.', 'title' => 'Titel', 'title_hint' => 'Nutze einen klaren und einprägsamen Episodennamen. Gib hier nicht die Episoden- oder Staffelnummern an.', @@ -114,12 +116,12 @@ return [ 'bonus_hint' => 'Zusätzliche Inhalte für die Sendung (zum Beispiel hinter den Kulissen, Informationen oder Interviews mit dem Team) oder übergreifende Promotionsinhalte für eine andere Show', ], 'premium_title' => 'Premium', - 'premium' => 'Episode must be accessible to premium subscribers only', + 'premium' => 'Diese Episode darf nur für Premium-Abonnenten zugänglich sein', 'parental_advisory' => [ - 'label' => 'Elternberatung', + 'label' => 'Hinweis für Erziehungsberechtigte', 'hint' => 'Enthält die Folge anstößige Inhalte?', 'undefined' => 'undefiniert', - 'clean' => 'Zurücksetzen', + 'clean' => 'Sauber', 'explicit' => 'Anstößig', ], 'show_notes_section_title' => 'Notizen anzeigen', @@ -131,15 +133,15 @@ return [ 'Dieser Text wird am Ende jeder Episodenbeschreibung hinzugefügt, es ist ein guter Ort, um zum Beispiel Ihre sozialen Links einzufügen.', 'additional_files_section_title' => 'Zusätzliche Dateien', 'additional_files_section_subtitle' => - 'These files may be used by other platforms to provide better experience to your audience. See the {podcastNamespaceLink} for more information.', + 'Diese Dateien könnten von anderen Plattformen genutzt werden, um eine bessere Nutzererfahrung zu bieten. Weitere Informationen finden Sie unter {podcastNamespaceLink}.', 'location_section_title' => 'Standort', 'location_section_subtitle' => 'Über welchen Ort handelt diese Folge?', 'location_name' => 'Standortname oder Adresse', 'location_name_hint' => 'Dies kann ein realer oder fiktiver Ort sein', 'transcript' => 'Transkript (Untertitel)', - 'transcript_hint' => 'Nur .srt ist erlaubt.', + 'transcript_hint' => 'Nur .srt oder .vtt sind erlaubt.', 'transcript_download' => 'Transkript herunterladen', - 'transcript_file' => 'Transkriptionsdatei (.srt)', + 'transcript_file' => 'Transkriptdatei (.srt oder .vtt)', 'transcript_remote_url' => 'Remote-URL für Transkript', 'transcript_file_delete' => 'Transkriptionsdatei löschen', 'chapters' => 'Kapitel', @@ -153,9 +155,9 @@ return [ 'Wenn du RSS-Tags benötigst, die Castopod nicht behandelt, setze diese hier.', 'custom_rss' => 'Eigene RSS-Tags für die Episode', 'custom_rss_hint' => 'Dies wird innerhalb des ❬item❭ Tags eingefügt.', - 'block' => 'Episode should be hidden from public catalogues', + 'block' => 'Episode soll vor öffentlichen Katalogen versteckt werden', 'block_hint' => - 'The episode show or hide status: toggling this on prevents the episode from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'Die Episode zeigt oder versteckt den Status: Beim Einschalten dieser Option wird verhindert, dass die Episode in Apple Podcasts, Google Podcasts und alle Apps von Drittanbietern, die Inhalte aus diesen Verzeichnissen ziehen, erscheint. (Nicht garantiert)', 'submit_create' => 'Folge erstellen', 'submit_edit' => 'Folge speichern', ], @@ -169,7 +171,7 @@ return [ 'publication_method' => [ 'now' => 'Jetzt', 'schedule' => 'Zeitplan', - 'with_podcast' => 'Publish alongside podcast', + 'with_podcast' => 'Zusammen mit dem Podcast veröffentlichen', ], 'scheduled_publication_date' => 'Geplantes Veröffentlichungsdatum', 'scheduled_publication_date_clear' => 'Veröffentlichungsdatum löschen', @@ -183,21 +185,21 @@ return [ 'message_warning_submit' => 'Trotzdem veröffentlichen', ], 'publish_date_edit_form' => [ - 'new_publication_date' => 'New publication date', - 'new_publication_date_hint' => 'Must be set to a past date.', - 'submit' => 'Edit publication date', + 'new_publication_date' => 'Neues Veröffentlichungsdatum', + 'new_publication_date_hint' => 'Muss auf ein vergangenes Datum gesetzt werden.', + 'submit' => 'Veröffentlichungsdatum bearbeiten', ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", + "Das Zurückziehen dieser Episode löscht alle damit verbundenen Kommentare und Beiträge und entferne sie aus dem RSS-Feed des Podcasts.", 'understand' => 'Ich verstehe, ich möchte die Episode zurückziehen', 'submit' => 'Zurückziehen', ], 'delete_form' => [ 'disclaimer' => - "Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.", + "Das Löschen der Episode wird auch alle verknüpften Mediendateien, Kommentare, Videoclips und Soundbites löschen.", 'understand' => 'Ich verstehe, ich möchte die Folge löschen', - 'submit' => 'Delete', + 'submit' => 'Löschen', ], 'embed' => [ 'title' => 'Einbettbarer Spieler', @@ -210,4 +212,14 @@ return [ 'light' => 'Hell', 'light-transparent' => 'Hell (transparent)', ], + 'publication_status_banner' => [ + 'draft_mode' => 'Entwurfsmodus', + 'text' => '{publication_status, select, + published {Diese Episode ist noch nicht veröffentlicht.} + scheduled {Diese Episode ist für die Veröffentlichung geplant am {publication_date}.} + with_podcast {Diese Episode wird zur gleichen Zeit wie der Podcast veröffentlicht.} + other {Diese Episode ist noch nicht veröffentlicht.} + }', + 'preview' => 'Vorschau', + ], ]; diff --git a/modules/Admin/Language/de/Fediverse.php b/modules/Admin/Language/de/Fediverse.php index 5a22c8dc..1c2a060d 100644 --- a/modules/Admin/Language/de/Fediverse.php +++ b/modules/Admin/Language/de/Fediverse.php @@ -12,7 +12,7 @@ return [ 'messages' => [ 'actorNotFound' => 'Das Konto konnte nicht gefunden werden!', 'blockActorSuccess' => '{actor} wurde blockiert!', - 'unblockActorSuccess' => 'Darsteller wurde freigegeben!', + 'unblockActorSuccess' => 'Benutzer wurde entsperrt!', 'blockDomainSuccess' => '{domain} wurde blockiert!', 'unblockDomainSuccess' => '{domain} wurde freigegeben!', ], diff --git a/modules/Admin/Language/de/Install.php b/modules/Admin/Language/de/Install.php index d901d1c5..89a8b267 100644 --- a/modules/Admin/Language/de/Install.php +++ b/modules/Admin/Language/de/Install.php @@ -20,10 +20,10 @@ return [ 'Um optional CDN und/oder einen externen Analysedienst verwenden zu können, müssen die Daten eingegeben werden.', 'admin_gateway' => 'Admin-Gateway', 'admin_gateway_hint' => - 'Die Route zum Zugriff auf den Admin-Bereich (z.B. https://example.com/cp-admin). Standardmäßig als cp-admin festgelegt. Wir empfehlen, sie aus Sicherheitsgründen zu ändern.', + 'Der Pfad zum Zugriff auf den Admin-Bereich (z.B. https://example.com/cp-admin). Standardmäßig als cp-admin festgelegt. Wir empfehlen, sie aus Sicherheitsgründen zu ändern.', 'auth_gateway' => 'Auth-Gateway', 'auth_gateway_hint' => - 'Die Route zum Zugriff auf die Authentifizierungsseiten (z. B. https://example.com/cp-auth). Standardmäßig als cp-auth gesetzt. Wir empfehlen, sie aus Sicherheitsgründen zu ändern.', + 'Der Pfad zum Zugriff auf die Authentifizierungsseiten (z. B. https://example.com/cp-auth). Standardmäßig als cp-auth gesetzt. Wir empfehlen, sie aus Sicherheitsgründen zu ändern.', 'database_config' => 'Datenbankkonfiguration', 'database_config_hint' => 'Castopod muss sich mit der MySQL-Datenbank (oder MariaDB) verbinden. Wenn diese erforderlichen Informationen nicht verfügbar sind, wenden Sie sich bitte an Ihren Serveradministrator.', @@ -31,12 +31,12 @@ return [ 'db_name' => 'Datenbankname', 'db_username' => 'Datenbankbenutzername', 'db_password' => 'Datenbankpasswort', - 'db_prefix' => 'Datenbankpräfix', + 'db_prefix' => 'Tabellenpräfix', 'db_prefix_hint' => - "Das Präfix der Castopod-Tabellennamen. Nicht anpassen, wenn nicht gewiss, was damit gemeint ist.", + "Das Präfix der Castopod-Tabellennamen. Nicht anpassen, wenn du nicht weißt, was damit gemeint ist.", 'cache_config' => 'Cachekonfiguration', 'cache_config_hint' => - 'Wählen Sie Ihren bevorzugten Cache-Handler. Standardwert verwenden, wenn nicht gewiss, was damit gemeint ist.', + 'Wählen Sie Ihren bevorzugten Cache-Handler. Standardwert verwenden, wenn Sie nicht wissen, was damit gemeint ist.', 'cache_handler' => 'Cache-Handler', 'cacheHandlerOptions' => [ 'file' => 'Datei', diff --git a/modules/Admin/Language/de/Navigation.php b/modules/Admin/Language/de/Navigation.php index 04fad137..f7b7b0ba 100644 --- a/modules/Admin/Language/de/Navigation.php +++ b/modules/Admin/Language/de/Navigation.php @@ -12,12 +12,14 @@ return [ 'toggle_sidebar' => 'Seitenleiste ein/aus', 'go_to_website' => 'Gehe zur Webseite', 'go_to_admin' => 'Gehe zu Admin', + 'not-authorized' => 'Nicht berechtigt', 'dashboard' => 'Übersicht', 'admin' => 'Startseite', 'podcasts' => 'Podcasts', 'podcast-list' => 'Alle Podcasts', 'podcast-create' => 'Neuer Podcast', - 'podcast-import' => 'Podcast importieren', + 'all-podcast-imports' => 'Alle Podcast-Importe', + 'podcast-imports-add' => 'Podcast importieren', 'persons' => 'Mitwirkende', 'person-list' => 'Alle Mitwirkenden', 'person-create' => 'Neuer Mitwirkender', @@ -33,6 +35,7 @@ return [ 'settings' => 'Einstellungen', 'settings-general' => 'Allgemein', 'settings-theme' => 'Erscheinungsbild', + 'admin-about' => 'Über', 'account' => [ 'my-account' => 'Mein Konto', 'change-password' => 'Passwort ändern', diff --git a/modules/Admin/Language/de/Notifications.php b/modules/Admin/Language/de/Notifications.php index 2b139d51..88439bb4 100644 --- a/modules/Admin/Language/de/Notifications.php +++ b/modules/Admin/Language/de/Notifications.php @@ -9,11 +9,11 @@ declare(strict_types=1); */ return [ - 'title' => 'Notifications', - 'reply' => '{actor_username} replied to your post', - 'favourite' => '{actor_username} favourited your post', - 'reblog' => '{actor_username} shared your post', - 'follow' => '{actor_username} started following you', - 'no_notifications' => 'No notifications', - 'mark_all_as_read' => 'Mark all as read', + 'title' => 'Benachrichtigungen', + 'reply' => '{actor_username} hat auf Ihren Beitrag geantwortet', + 'favourite' => '{actor_username} hat Ihren Beitrag favorisiert', + 'reblog' => '{actor_username} hat Ihren Beitrag geteilt', + 'follow' => '{actor_username} folgt Ihnen jetzt', + 'no_notifications' => 'Keine Benachrichtigungen', + 'mark_all_as_read' => 'Alle als gelesen markieren', ]; diff --git a/modules/Admin/Language/de/Page.php b/modules/Admin/Language/de/Page.php index e5e5febb..801ff2e7 100644 --- a/modules/Admin/Language/de/Page.php +++ b/modules/Admin/Language/de/Page.php @@ -18,7 +18,7 @@ return [ 'delete' => 'Seite löschen', 'form' => [ 'title' => 'Titel', - 'permalink' => 'Permanenter Link', + 'permalink' => 'Permalink', 'content' => 'Inhalt', 'submit_create' => 'Seite erstellen', 'submit_edit' => 'Speichern', diff --git a/modules/Admin/Language/de/Pager.php b/modules/Admin/Language/de/Pager.php index d2bddfb6..54c96cbe 100644 --- a/modules/Admin/Language/de/Pager.php +++ b/modules/Admin/Language/de/Pager.php @@ -10,10 +10,10 @@ declare(strict_types=1); return [ 'pageNavigation' => 'Seiten-Navigation', - 'first' => 'Erste', - 'previous' => 'Zurück', - 'next' => 'Weiter', - 'last' => 'Letzte', + 'first' => 'Erste Seite', + 'previous' => 'Vorherige', + 'next' => 'Nächste', + 'last' => 'Letzte Seite', 'older' => 'Älter', 'newer' => 'Neuer', 'invalidTemplate' => '{0} ist kein gültiges Pager Template.', diff --git a/modules/Admin/Language/de/Person.php b/modules/Admin/Language/de/Person.php index 96724be0..a9287867 100644 --- a/modules/Admin/Language/de/Person.php +++ b/modules/Admin/Language/de/Person.php @@ -24,7 +24,7 @@ return [ 'form' => [ 'avatar' => 'Profilbild', 'avatar_size_hint' => - 'Avatar must be squared and at least 400px wide and tall.', + 'Das Profilbild muss quadratisch und mindestens 400px breit und hoch sein.', 'full_name' => 'Vollständiger Name', 'full_name_hint' => 'Dies ist der vollständige Name oder der Alias der Person.', 'unique_name' => 'Eindeutiger Name', @@ -44,7 +44,7 @@ return [ 'Es können eine oder mehrere Personen mit der gleichen Rolle ausgewählt werden. Die Personen müssen zuerst erstellt werden.', 'roles' => 'Rollen', 'roles_hint' => - 'Es können sowhol keine, eine als auch mehrere Rollen für eine Person ausgewählt werden.', + 'Es können sowohl keine, eine als auch mehrere Rollen für eine Person ausgewählt werden.', 'submit_add' => 'Person:en hinzufügen', 'remove' => 'Entfernen', ], @@ -57,9 +57,9 @@ return [ 'Es können eine oder mehrere Personen mit der gleichen Rolle ausgewählt werden. Die Personen müssen zuerst erstellt werden.', 'roles' => 'Rollen', 'roles_hint' => - 'Es können sowhol keine, eine als auch mehrere Rollen für eine Person ausgewählt werden.', + 'Es können sowohl keine, eine als auch mehrere Rollen für eine Person ausgewählt werden.', 'submit_add' => 'Person:en hinzufügen', 'remove' => 'Entfernen', ], - 'credits' => 'Danksagungen', + 'credits' => 'Credits', ]; diff --git a/modules/Admin/Language/de/Platforms.php b/modules/Admin/Language/de/Platforms.php index 5e55a2a3..41751316 100644 --- a/modules/Admin/Language/de/Platforms.php +++ b/modules/Admin/Language/de/Platforms.php @@ -9,9 +9,22 @@ declare(strict_types=1); */ return [ - 'title' => 'Plattformen', + 'title' => [ + 'podcasting' => 'Podcast-Plattformen', + 'social' => 'Soziale Medien', + 'funding' => 'Finanzierungslinks', + ], + 'website' => 'Webseite', 'home_url' => 'Gehe zu {platformName} Webseite', + 'register' => 'Registrieren', 'submit_url' => 'Sende deinen Podcast an {platformName}', + 'your_link' => 'Dein Link', + 'your_id' => [ + 'podcasting' => 'Deine ID', + 'social' => 'Deine ID', + 'funding' => 'Deine CTA', + ], + 'your_cta' => 'Dein Aufruf zur Aktion', 'visible' => 'Auf Podcast-Homepage anzeigen?', 'on_embed' => 'Auf einbettbarem Player anzeigen?', 'remove' => 'Entferne {platformName}', diff --git a/modules/Admin/Language/de/Podcast.php b/modules/Admin/Language/de/Podcast.php index 8cff53a1..d2165791 100644 --- a/modules/Admin/Language/de/Podcast.php +++ b/modules/Admin/Language/de/Podcast.php @@ -13,65 +13,78 @@ return [ 'no_podcast' => 'Kein Podcast gefunden!', 'create' => 'Podcast erstellen', 'import' => 'Podcast importieren', + 'all_imports' => 'Podcast-Importe', 'new_episode' => 'Neue Folge', 'view' => 'Podcast ansehen', 'edit' => 'Podcast bearbeiten', - 'publish' => 'Publish podcast', - 'publish_edit' => 'Edit publication', + 'publish' => 'Podcast veröffentlichen', + 'publish_edit' => 'Veröffentlichung bearbeiten', 'delete' => 'Podcast löschen', 'see_episodes' => 'Episoden ansehen', - 'see_contributors' => 'Administratoren anzeigen', + 'see_contributors' => 'Mitwirkende anzeigen', + 'monetization_other' => 'Sonstige Monetarisierung', 'go_to_page' => 'Gehe zur Seite', 'latest_episodes' => 'Neueste Folgen', 'see_all_episodes' => 'Alle Folgen anzeigen', - 'draft' => 'Draft', + 'draft' => 'Entwurf', 'messages' => [ - 'createSuccess' => 'Podcast successfully created!', + 'createSuccess' => 'Podcast erfolgreich erstellt!', 'editSuccess' => 'Der Podcast wurde erfolgreich aktualisiert!', 'importSuccess' => 'Der Podcast wurde erfolgreich importiert!', - 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', - 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, - cover {cover} - banner {banner} - other {media} + 'deleteSuccess' => 'Podcast @{podcast_handle} erfolgreich gelöscht!', + 'deletePodcastMediaError' => 'Fehler beim Löschen des Podcast-{type, select, + cover {Covers} + banner {Banners} + other {Media} }.', - 'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select, - transcript {transcript} - chapters {chapters} - image {cover} - audio {audio} - other {media} - }.', - 'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.', - 'podcastFeedUpdateSuccess' => 'Successful update: {number_of_new_episodes, plural, - one {# episode was} - other {# episodes were} - } added to the podcast!', - 'podcastFeedUpToDate' => 'Podcast is already up to date.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', - 'publishError' => 'This podcast is either already published or scheduled for publication.', - 'publishEditError' => 'This podcast is not scheduled for publication.', - 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', - 'scheduleDateError' => 'Schedule date must be set!', + 'deleteEpisodeMediaError' => 'Fehler beim Löschen {type, select, + transcript {der Abschrift} + chapters {der Kapitel} + image {des Covers} + audio {der Audio} + other {der Medien} + } von Episode {episode_slug}.', + 'deletePodcastMediaFolderError' => 'Fehler beim Löschen des Podcast-Medienordners {folder_path}. Sie können ihn manuell von der Festplatte löschen.', + 'podcastFeedUpdateSuccess' => 'Erfolgreiche Aktualisierung: {number_of_new_episodes, plural, + one {# Episode wurde} + other {# Episoden wurden} + } zum Podcast hinzugefügt!', + 'podcastFeedUpToDate' => 'Der Podcast ist bereits auf dem neuesten Stand.', + 'publishError' => 'Dieser Podcast ist entweder bereits veröffentlicht oder zur Veröffentlichung geplant.', + 'publishEditError' => 'Dieser Podcast ist nicht zur Veröffentlichung geplant.', + 'publishCancelSuccess' => 'Veröffentlichung des Podcasts erfolgreich abgebrochen!', + 'scheduleDateError' => 'Veröffentlichungsdatum muss gesetzt sein!', ], 'form' => [ 'identity_section_title' => 'Podcast-Identität', 'identity_section_subtitle' => 'Diese Felder erlauben es dir, Aufmerksamkeit zu bekommen.', + 'fediverse_section_title' => 'Fediverse-Identität', + 'cover' => 'Podcast-Cover', - 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'cover_size_hint' => 'Das Cover muss quadratisch und mindestens 1400px breit und hoch sein.', 'banner' => 'Podcast-Banner', - 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', + 'banner_size_hint' => 'Der Banner muss ein 3:1-Verhältnis haben und mindestens 1500px breit sein.', 'banner_delete' => 'Podcast-Banner löschen', 'title' => 'Titel', 'handle' => 'Identifikator', 'handle_hint' => - 'Verwendet um den Podcast zu identifizieren. Großbuchstaben, Kleinbuchstaben, Zahlen und Unterstriche sind erlaubt.', + 'Wird genutzt, um den Podcast zu identifizieren. Großbuchstaben, Kleinbuchstaben, Zahlen und Unterstriche sind erlaubt.', 'type' => [ 'label' => 'Typ', 'episodic' => 'Episodisch', 'episodic_hint' => 'Wenn Folgen ohne bestimmte Reihenfolge abgespielt werden sollen. Neueste Folgen werden zuerst angezeigt.', 'serial' => 'Seriell', - 'serial_hint' => 'Wenn Folgen in bestimmter Reihenfolge abgespielt werden sollen. Älteste Folgen werden zuerst angezeigt.', + 'serial_hint' => 'Wenn Episoden in sequenzieller Reihenfolge konsumiert werden sollen. Episoden werden in numerischer Reihenfolge angezeigt.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Beschreibt einen Feed für eine Podcast-Show.', + 'music' => 'Musik', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Hörbuch', + 'audiobook_hint' => 'Spezifische Arten von Audio mit einem Eintrag pro Feed, oder wenn Elemente Kapitel innerhalb des Buches darstellen.', ], 'description' => 'Beschreibung', 'classification_section_title' => 'Klassifikation', @@ -80,12 +93,12 @@ return [ 'language' => 'Sprache', 'category' => 'Kategorie', 'category_placeholder' => 'Kategorie wählen...', - 'other_categories' => 'Andere Kategorieen', + 'other_categories' => 'Andere Kategorien', 'parental_advisory' => [ 'label' => 'Hinweis an Eltern', 'hint' => 'Enthält die Folge anstößige Inhalte?', 'undefined' => 'nicht definiert', - 'clean' => 'Zurücksetzen', + 'clean' => 'Sauber', 'explicit' => 'Anstößig', ], 'author_section_title' => 'Autor', @@ -96,44 +109,51 @@ return [ 'owner_email' => 'E-Mail des Eigentümers', 'owner_email_hint' => 'Wird von den meisten Plattformen verwendet werden, um den Podcast-Besitz zu überprüfen. Sichtbar im öffentlichen RSS-Feed.', + 'is_owner_email_removed_from_feed' => 'Entferne die Eigentümer-E-Mail aus dem öffentlichen RSS-Feed', + 'is_owner_email_removed_from_feed_hint' => 'Möglicherweise müssen Sie die E-Mail vorübergehend freigeben, damit ein Verzeichnis Ihren Podcast-Besitz verifizieren kann.', 'publisher' => 'Herausgeber', 'publisher_hint' => 'Die Gruppe, die für die Erstellung des Podcasts verantwortlich ist. Oft bezogen auf die Muttergesellschaft oder das Netzwerk eines Podcasts. Dieses Feld wird manchmal als \'Autor\' bezeichnet.', 'copyright' => 'Urheberrecht', 'location_section_title' => 'Standort', - 'location_section_subtitle' => 'Über welchen Ort handelt dieser Podcast?', + 'location_section_subtitle' => 'Um welchen Ort geht es in diesem Podcast?', 'location_name' => 'Standortname oder Adresse', - 'location_name_hint' => 'Dies kann ein echter Ort oder fiktiv sein', + 'location_name_hint' => 'Dies kann ein echter oder ein fiktiver Ort sein', 'monetization_section_title' => 'Monetarisierung', 'monetization_section_subtitle' => - 'Geld dank der Zuhöhrer verdienen.', + 'Geld dank der Zuhörer verdienen.', 'premium' => 'Premium', - 'premium_by_default' => 'Episodes must be set as premium by default', - 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'premium_by_default' => 'Episoden müssen standardmäßig als Premium festgelegt werden', + 'premium_by_default_hint' => 'Podcast-Episoden werden standardmäßig als Premium markiert. Sie können dennoch einzelne Episoden, Trailer oder Boni als öffentlich festlegen.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Besuche dein OP3-Dashboard (externer Link)', + 'op3_hint' => 'Werten Sie Ihre Analysedaten mit OP3 auf, einem quelloffenen und vertrauenswürdigen Analysedienst eines Drittanbieters. Teilen, validieren und vergleichen Sie Ihre Analysedaten in dem offenen Podcast-Ökosystem.', + 'op3_enable' => 'OP3-Analysedienst aktivieren', + 'op3_enable_hint' => 'Aus Sicherheitsgründen werden die Analysedaten von Premium-Episoden nicht mit OP3 geteilt.', 'payment_pointer' => 'Zahlungsadresse (Payment Pointer) für Web-Monetarisierung', 'payment_pointer_hint' => 'Hier erhalten Sie dank Monetarisierung Geld', 'advanced_section_title' => 'Erweiterte Einstellungen', 'advanced_section_subtitle' => - 'Wenn RSS-Tags benötigt werden, die Castopod nicht verwendet, können diese hier gesetz werden.', + 'Wenn RSS-Tags benötigt werden, die Castopod nicht verwendet, können diese hier gesetzt werden.', 'custom_rss' => 'Eigene RSS-Tags für den Podcast', 'custom_rss_hint' => 'Dies wird innerhalb des ❬channel❭ Tags eingefügt.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', 'new_feed_url' => 'Neue Feed-URL', 'new_feed_url_hint' => 'Benutzen Sie dieses Feld, wenn Sie zu einer anderen Domain oder Podcast-Plattform wechseln. Standardmäßig wird der Wert auf die aktuelle RSS URL gesetzt, wenn der Podcast importiert wird.', - 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', + 'old_feed_url' => 'Alte Feed-URL', 'partnership' => 'Partnerschaft:en', 'partner_id' => 'ID', 'partner_link_url' => 'Link', 'partner_image_url' => 'Bildadresse', 'partner_id_hint' => 'Ihre eigene Partner-ID', 'partner_link_url_hint' => 'Die generische Partnerlink-Adresse', - 'partner_image_url_hint' => 'Die generische Partnerbild-adresse', - 'status_section_title' => 'Status', - 'block' => 'Podcast should be hidden from public catalogues', + 'partner_image_url_hint' => 'Die generische Partnerbild-Adresse', + 'block' => 'Podcast soll vor öffentlichen Katalogen versteckt werden', 'block_hint' => - 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'Der Podcast zeigt oder versteckt den Status: Beim Einschalten dieser Option wird verhindert, dass der Podcast in Apple Podcasts, Google Podcasts und alle Apps von Drittanbietern, die Inhalte aus diesen Verzeichnissen ziehen, erscheint. (Ohne Garantie!)', 'complete' => 'Der Podcast wird keine neuen Folgen erhalten', 'lock' => 'Schütze den Podcast davor kopiert zu werden', 'lock_hint' => @@ -144,8 +164,8 @@ return [ 'category_options' => [ 'uncategorized' => 'unkategorisiert', 'arts' => 'Kunst', - 'business' => 'Geschäftlich', - 'comedy' => 'Komödie', + 'business' => 'Geschäftliches', + 'comedy' => 'Comedy', 'education' => 'Bildung', 'fiction' => 'Fiktion', 'government' => 'Regierung', @@ -168,12 +188,12 @@ return [ 'food' => 'Essen', 'performing_arts' => 'Darstellende Kunst', 'visual_arts' => 'Visuelle Kunst', - 'careers' => 'Karrieren', - 'entrepreneurship' => 'Entrepreneurship', + 'careers' => 'Karriere', + 'entrepreneurship' => 'Unternehmertum', 'investing' => 'Investment', 'management' => 'Management', 'marketing' => 'Marketing', - 'non_profit' => 'Gemeinnützig', + 'non_profit' => 'Gemeinnützigkeit', 'comedy_interviews' => 'Comedy-Interviews', 'improv' => 'Improvisation', 'stand_up' => 'Stand-Up', @@ -255,36 +275,36 @@ return [ 'tv_reviews' => 'TV-Kritiken', ], 'publish_form' => [ - 'back_to_podcast_dashboard' => 'Back to podcast dashboard', - 'post' => 'Your announcement post', + 'back_to_podcast_dashboard' => 'Zurück zum Podcast-Dashboard', + 'post' => 'Dein Ankündigungsbeitrag', 'post_hint' => - "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", - 'message_placeholder' => 'Write your message…', - 'submit' => 'Publish', - 'publication_date' => 'Publication date', + "Schreiben Sie eine Nachricht, um die Veröffentlichung Ihres Podcasts anzukündigen. Diese Nachricht wird auf der Homepage des Podcasts erscheinen.", + 'message_placeholder' => 'Schreiben Sie Ihre Nachricht…', + 'submit' => 'Veröffentlichen', + 'publication_date' => 'Veröffentlichungsdatum', 'publication_method' => [ - 'now' => 'Now', - 'schedule' => 'Schedule', + 'now' => 'Jetzt', + 'schedule' => 'Planen', ], - 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date' => 'Geplantes Veröffentlichungsdatum', 'scheduled_publication_date_hint' => - 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', - 'submit_edit' => 'Edit publication', - 'cancel_publication' => 'Cancel publication', - 'message_warning' => 'You did not write a message for your announcement post!', - 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', - 'message_warning_submit' => 'Publish anyway', + 'Du kannst die Veröffentlichung des Podcasts planen, indem du ein zukünftiges Veröffentlichungsdatum festlegst. Dieses Feld muss als YYYY-MM-TT HH:mm formatiert werden', + 'submit_edit' => 'Veröffentlichung bearbeiten', + 'cancel_publication' => 'Veröffentlichung abbrechen', + 'message_warning' => 'Sie haben keinen Text für Ihren Ankündigungsbeitrag geschrieben!', + 'message_warning_hint' => 'Eine Nachricht erhöht das soziale Engagement, was zu einer besseren Sichtbarkeit des Podcasts führt.', + 'message_warning_submit' => 'Dennoch veröffentlichen', ], 'publication_status_banner' => [ - 'draft_mode' => 'draft mode', - 'not_published' => 'This podcast is not yet published.', - 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + 'draft_mode' => 'Entwurfsmodus', + 'not_published' => 'Dieser Podcast ist noch nicht veröffentlicht.', + 'scheduled' => 'Dieser Podcast ist für eine Veröffentlichung am {publication_date} vorgesehen.', ], 'delete_form' => [ 'disclaimer' => - "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", - 'understand' => 'I understand, I want the podcast to be permanently deleted', - 'submit' => 'Delete', + "Beim Löschen des Podcasts werden alle damit verbundenen Episoden, Mediendateien, Beiträge und Analysen gelöscht. Diese Aktion ist unumkehrbar, Sie können diese danach nicht mehr abrufen.", + 'understand' => 'Ich verstehe, ich möchte, dass der Podcast dauerhaft gelöscht wird', + 'submit' => 'Löschen', ], 'by' => 'Von {publisher}', 'season' => 'Staffel {seasonNumber}', @@ -294,12 +314,12 @@ return [ 'no_episode' => 'Keine Folge gefunden!', 'follow' => 'Folgen', 'followers' => '{numberOfFollowers, plural, - one {# follower} - other {# followers} + one {# Follower} + other {# Follower} }', 'posts' => '{numberOfPosts, plural, - one {# post} - other {# posts} + one {# Beitrag} + other {# Beiträge} }', 'activity' => 'Aktivitäten', 'episodes' => 'Folgen', diff --git a/modules/Admin/Language/de/PodcastImport.php b/modules/Admin/Language/de/PodcastImport.php deleted file mode 100644 index 20153bc7..00000000 --- a/modules/Admin/Language/de/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'Der zu importierende Podcast', - 'old_podcast_section_subtitle' => - 'Stellen Sie sicher, dass Sie die Rechte für diesen Podcast besitzen, bevor Sie ihn importieren. Vervielfältigung und Ausstrahlung eines Podcasts ohne die entsprechenden Rechte sind Piraterie und strafbar.', - 'imported_feed_url' => 'Feed-URL', - 'imported_feed_url_hint' => 'Der Feed muss im xml oder RSS-Format sein.', - 'new_podcast_section_title' => 'Der neue Podcast', - 'advanced_params_section_title' => 'Erweiterte Parameter', - 'advanced_params_section_subtitle' => - 'Behalten Sie die Standardwerte, wenn Sie keine Ahnung haben, wofür die Felder sind.', - 'slug_field' => 'Feld zum Berechnen der Episoden-URL (epiode slug)', - 'description_field' => - 'Quellfeld für Episodenbeschreibung verwendet / Notizen anzeigen', - 'force_renumber' => 'Erzwinge Neu-Nummerierung der Folgen', - 'force_renumber_hint' => - 'Verwende dies, wenn dein Podcast keine Episodennummern hat, aber du diese während des Imports setzen möchtest.', - 'season_number' => 'Staffelnummer', - 'season_number_hint' => - 'Benutze dies, wenn dein Podcast keine Staffelnummer hat, aber du eine beim Import setzen möchtest. Lasse es andernfalls leer.', - 'max_episodes' => 'Maximale Anzahl der zu importierenden Episoden', - 'max_episodes_hint' => 'Leer lassen um alle Episoden zu importieren', - 'lock_import' => - 'Dieser Feed ist geschützt. Du kannst ihn nicht importieren. Wenn du der Besitzer bist, entferne den Schutz auf der Ursprungsplattform.', - 'submit' => 'Podcast importieren', -]; diff --git a/modules/Admin/Language/de/PodcastNavigation.php b/modules/Admin/Language/de/PodcastNavigation.php index 088887f3..6fa2e17d 100644 --- a/modules/Admin/Language/de/PodcastNavigation.php +++ b/modules/Admin/Language/de/PodcastNavigation.php @@ -10,14 +10,17 @@ declare(strict_types=1); return [ 'go_to_page' => 'Zur Podcast-Seite gehen', + 'rss_feed' => 'RSS-Feed', 'dashboard' => 'Podcast-Dashboard', 'podcast-view' => 'Startseite', 'podcast-edit' => 'Podcast bearbeiten', 'podcast-persons-manage' => 'Mitwirkende verwalten', + 'podcast-imports' => 'Podcast-Importe', + 'podcast-imports-sync' => 'Feeds synchronisieren', 'episodes' => 'Folgen', 'episode-list' => 'Alle Episoden', 'episode-create' => 'Neue Episoden', - 'analytics' => 'Analysen', + 'analytics' => 'Statistiken', 'podcast-analytics' => 'Zuhörer-Übersicht', 'podcast-analytics-webpages' => 'Webseiten-Besuche', 'podcast-analytics-locations' => 'Standorte', @@ -25,14 +28,15 @@ return [ 'podcast-analytics-players' => 'Podcast-Player', 'podcast-analytics-listening-time' => 'Hörzeit', 'podcast-analytics-time-periods' => 'Zeiträume', - 'premium' => 'Premium', - 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', - 'contributors' => 'Administratoren', + 'monetization' => 'Monetarisierung', + 'subscription-list' => 'Alle Abonnements', + 'subscription-create' => 'Abonnement hinzufügen', + 'contributors' => 'Mitwirkende', 'contributor-list' => 'Alle Unterstützer', - 'contributor-add' => 'Administrator hinzufügen', - 'platforms' => 'Externe Plattformen', - 'platforms-podcasting' => 'Podcasting', + 'contributor-add' => 'Mitwirkenden hinzufügen', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcast-Apps', 'platforms-social' => 'Soziale Netzwerke', - 'platforms-funding' => 'Finanzierung', + 'platforms-funding' => 'Finanzierungslinks', + 'podcast-monetization-other' => 'Andere', ]; diff --git a/modules/Admin/Language/de/Settings.php b/modules/Admin/Language/de/Settings.php index 9484c30e..d046a40e 100644 --- a/modules/Admin/Language/de/Settings.php +++ b/modules/Admin/Language/de/Settings.php @@ -14,8 +14,8 @@ return [ 'title' => 'Instanz', 'site_icon' => 'Webseiten-Icon', 'site_icon_delete' => 'Lösche Webseiten-Icon', - 'site_icon_hint' => 'Webseiten-Icons sind das, was Sie auf Ihrem Browser Tabs, Lesezeichenleiste und wenn Sie eine Website als Verknüpfung auf mobilen Geräten hinzufügen, sehen.', - 'site_icon_helper' => 'Icon must be squared and at least 512px wide and tall.', + 'site_icon_hint' => 'Webseiten-Icons sind das, was Sie in Ihren Browser-Tabs, der Lesezeichenleiste und als Verknüpfung auf mobilen Geräten sehen.', + 'site_icon_helper' => 'Das Icon muss quadratisch und mindestens 512px breit und hoch sein.', 'site_name' => 'Seitenname', 'site_description' => 'Seitenbeschreibung', 'submit' => 'Speichern', @@ -35,10 +35,10 @@ return [ 'reset_counts_helper' => 'Diese Option wird alle Datenzähler neu berechnen und zurücksetzen (Anzahl der Follower, Beiträge, Kommentare, …).', 'rewrite_media' => 'Medien-Metadaten neu schreiben', 'rewrite_media_helper' => 'Diese Option wird alle überflüssigen Mediendateien löschen und neu erstellen (Bilder, Audiodateien, Transkripte, Kapitel …)', - 'rename_episodes_files' => 'Rename episode audio files', - 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', + 'rename_episodes_files' => 'Audiodateien der Episode umbenennen', + 'rename_episodes_files_hint' => 'Diese Option wird alle Audiodateien der Episode mit einer zufälligen Zeichenkette umbenennen. Benutzen Sie diese Option, wenn einer Ihrer privaten Episoden-Links durchsickert, da diese dadurch versteckt werden.', 'clear_cache' => 'Alle Caches löschen', - 'clear_cache_helper' => 'Diese Option leert den redis-Cache oder beschreibbare/cache-Dateien.', + 'clear_cache_helper' => 'Diese Option leert den redis-Cache oder beschreibbare/Cache-Dateien.', 'run' => 'Systempflege starten', 'runSuccess' => 'Die Systempflege wurde erfolgreich durchgeführt!', ], diff --git a/modules/Admin/Language/de/Soundbite.php b/modules/Admin/Language/de/Soundbite.php index 75f0103d..e8d6782f 100644 --- a/modules/Admin/Language/de/Soundbite.php +++ b/modules/Admin/Language/de/Soundbite.php @@ -10,22 +10,22 @@ declare(strict_types=1); return [ 'list' => [ - 'title' => 'Tonschnipsel', - 'soundbite' => 'Tonschnipsel', + 'title' => 'Soundbites', + 'soundbite' => 'Soundbite', ], 'messages' => [ - 'createSuccess' => 'Tonschnipsel wurde erfolgreich erstellt!', - 'deleteSuccess' => 'Tonschnipsel wurde erfolgreich entfernt!', + 'createSuccess' => 'Soundbite wurde erfolgreich erstellt!', + 'deleteSuccess' => 'Soundbite wurde erfolgreich entfernt!', ], 'form' => [ - 'title' => 'Neuer Tonschnipsel', - 'soundbite_title' => 'Tonschnipsel-Titel', + 'title' => 'Neues Soundbite', + 'soundbite_title' => 'Soundbite-Titel', 'start_time' => 'Beginne bei', 'duration' => 'Länge', - 'submit' => 'Tonschnipsel erstellen', + 'submit' => 'Soundbite erstellen', ], - 'play' => 'Tonschnipsel abspielen', + 'play' => 'Soundbite abspielen', 'stop' => 'Tonschnipsel stoppen', - 'create' => 'Neuer Tonschnipsel', - 'delete' => 'Tonschnipsel löschen', + 'create' => 'Neues Soundbite', + 'delete' => 'Soundbite löschen', ]; diff --git a/modules/Admin/Language/de/Validation.php b/modules/Admin/Language/de/Validation.php index f973bf67..bae6668c 100644 --- a/modules/Admin/Language/de/Validation.php +++ b/modules/Admin/Language/de/Validation.php @@ -13,6 +13,5 @@ return [ '{field} ist entweder kein Bild, oder es ist nicht breit oder hoch genug.', 'is_image_ratio' => '{field} ist entweder kein Bild oder nicht das richtige Verhältnis.', - 'validate_url' => - 'Das {field} -Feld muss eine gültige URL sein (z.B. https://example.com/).', + 'is_json' => '{field} enthält ungültiges JSON.', ]; diff --git a/modules/Admin/Language/de/VideoClip.php b/modules/Admin/Language/de/VideoClip.php index bffcd16c..ccf9841b 100644 --- a/modules/Admin/Language/de/VideoClip.php +++ b/modules/Admin/Language/de/VideoClip.php @@ -25,7 +25,7 @@ return [ 'passed_hint' => 'Clip wurde erfolgreich erstellt!', ], 'clip' => 'Clip', - 'duration' => 'Job Laufzeit', + 'duration' => 'Laufzeit', ], 'title' => 'Video-Clip: {videoClipLabel}', 'download_clip' => 'Clip herunterladen', @@ -33,7 +33,7 @@ return [ 'go_to_page' => 'Zur Clip-Seite gehen', 'retry' => 'Clip-Generierung wiederholen', 'delete' => 'Clip löschen', - 'logs' => 'Job-Logs', + 'logs' => 'Jobprotokoll', 'messages' => [ 'alreadyExistingError' => 'Der Videoclip, den Sie zu erstellen versuchen, existiert bereits!', 'addToQueueSuccess' => 'Videoclip wurde zur Warteschlange hinzugefügt und wartet darauf, erstellt zu werden!', @@ -57,9 +57,9 @@ return [ 'theme' => 'Wähle ein Design', 'start_time' => 'Beginne bei', 'duration' => 'Laufzeit', - 'trim_start' => 'Trimm-Start', - 'trim_end' => 'Trimm-Ende', - 'submit' => 'Erstelle Videoclip', + 'trim_start' => 'Startpunkt', + 'trim_end' => 'Ende trimmen', + 'submit' => 'Neuen Clip erstellen', ], 'requirements' => [ 'title' => 'Fehlende Anforderungen', diff --git a/modules/Admin/Language/el/AboutCastopod.php b/modules/Admin/Language/el/AboutCastopod.php new file mode 100644 index 00000000..df1c81f4 --- /dev/null +++ b/modules/Admin/Language/el/AboutCastopod.php @@ -0,0 +1,22 @@ + 'Περί Του Castopod', + 'host_name' => 'Όνομα εξυπηρετητή', + 'version' => 'Έκδοση Castopod', + 'php_version' => 'Έκδοση PHP', + 'os' => 'Λειτουργικό σύστημα', + 'languages' => 'Γλώσσες', + 'update_database' => 'Ενημέρωση Βάσης Δεδομένων', + 'messages' => [ + 'databaseUpdateSuccess' => 'Η βάση δεδομένων είναι ενημερωμένη!', + ], +]; diff --git a/modules/Admin/Language/el/Breadcrumb.php b/modules/Admin/Language/el/Breadcrumb.php index 38b8ec92..217f6edb 100644 --- a/modules/Admin/Language/el/Breadcrumb.php +++ b/modules/Admin/Language/el/Breadcrumb.php @@ -14,29 +14,34 @@ return [ ->gateway => 'Αρχική σελίδα', 'podcasts' => 'podcasts', 'episodes' => 'επεισόδια', - 'subscriptions' => 'subscriptions', + 'subscriptions' => 'συνδρομές', 'contributors' => 'συντελεστές', 'pages' => 'σελίδες', 'settings' => 'ρυθμίσεις', 'theme' => 'θέμα', + 'about' => 'σχετικά', 'add' => 'προσθήκη', 'new' => 'νέο', 'edit' => 'επεξεργασία', 'persons' => 'άτομα', 'publish' => 'δημοσίευση', 'publish-edit' => 'επεξεργασία δημοσίευσης', - 'publish-date-edit' => 'edit publication date', + 'publish-date-edit' => 'επεξεργασία ημερομηνίας δημοσίευσης', 'unpublish' => 'αναίρεση δημοσίευσης', 'delete' => 'διαγραφή', + 'remove' => 'αφαίρεση', 'fediverse' => 'fediverse', - 'block-lists' => 'λίστες αποκλεισμένων', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', 'users' => 'χρήστες', 'my-account' => 'ο λογαριασμός μου', 'change-password' => 'αλλαγή κωδικού πρόσβασης', - 'import' => 'εισαγωγή ροής', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', 'platforms' => 'πλατφόρμες', 'social' => 'κοινωνικά δίκτυα', 'funding' => 'χρηματοδότηση', + 'monetization-other' => 'other monetization', 'analytics' => 'αναλυτικά στοιχεία', 'locations' => 'τοποθεσίες', 'webpages' => 'ιστοσελίδες', @@ -47,6 +52,6 @@ return [ 'soundbites' => 'ήχοι', 'video-clips' => 'βίντεο κλιπς', 'embed' => 'ενσωματώσιμος αναπαραγωγέας', - 'notifications' => 'notifications', - 'suspend' => 'suspend', + 'notifications' => 'ειδοποιήσεις', + 'suspend' => 'αναστολή', ]; diff --git a/modules/Admin/Language/el/Charts.php b/modules/Admin/Language/el/Charts.php index be7ed2ee..247a77e7 100644 --- a/modules/Admin/Language/el/Charts.php +++ b/modules/Admin/Language/el/Charts.php @@ -35,6 +35,7 @@ return [ 'by_weekday' => 'Την ημέρα της εβδομάδας (για τις τελευταίες 60 ημέρες)', 'by_hour' => 'Κατά την ώρα της ημέρας (για τις τελευταίες 60 ημέρες)', 'podcast_by_bandwidth' => 'Ημερήσιο χρησιμοποιούμενο bandwidth (σε MB)', - 'total_storage_by_month' => 'Monthly storage (in MB)', - 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_storage_by_month' => 'Μηνιαία αποθήκευση (σε MB)', + 'total_bandwidth_by_month' => 'Μηνιαίο χρησιμοποιούμενο εύρος ζώνης (σε MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', ]; diff --git a/modules/Admin/Language/el/Common.php b/modules/Admin/Language/el/Common.php index 13b75af4..7bf5bbec 100644 --- a/modules/Admin/Language/el/Common.php +++ b/modules/Admin/Language/el/Common.php @@ -40,12 +40,13 @@ return [ ], 'upload_file' => 'Μεταφορτώστε ένα αρχείο', 'remote_url' => 'Απομακρυσμένη διεύθυνση URL', + 'save' => 'Save', ], 'play_episode_button' => [ 'play' => 'Αναπαραγωγή', 'playing' => 'Αναπαράγεται', ], 'size_limit' => 'Όριο μεγέθους: {0}.', - 'choose_interact' => 'Choose how to interact', - 'view' => 'View', + 'choose_interact' => 'Επιλέξτε τον τρόπο αλληλεπίδρασης', + 'view' => 'Προβολή', ]; diff --git a/modules/Admin/Language/el/Dashboard.php b/modules/Admin/Language/el/Dashboard.php index 881073fd..b780ea20 100644 --- a/modules/Admin/Language/el/Dashboard.php +++ b/modules/Admin/Language/el/Dashboard.php @@ -9,20 +9,20 @@ declare(strict_types=1); */ return [ - 'home' => 'Admin dashboard', - 'welcome_message' => 'Welcome to the admin area!', + 'home' => 'Πίνακας ελέγχου διαχειριστή', + 'welcome_message' => 'Καλώς ήρθατε στην περιοχή διαχείρισης!', 'podcasts' => [ 'title' => 'Podcasts', - 'not_found' => 'No published podcast', - 'last_published' => 'Last published on {lastPublicationDate}', + 'not_found' => 'Δεν υπάρχει δημοσιευμένο podcast', + 'last_published' => 'Τελευταία δημοσίευση στις {lastPublicationDate}', ], 'episodes' => [ - 'title' => 'Episodes', - 'not_found' => 'No published episode', - 'last_published' => 'Last published on {lastPublicationDate}', + 'title' => 'Επεισόδια', + 'not_found' => 'Κανένα δημοσιευμένο επεισόδιο', + 'last_published' => 'Τελευταία δημοσίευση στις {lastPublicationDate}', ], 'storage' => [ - 'title' => 'Storage', - 'subtitle' => '{totalUploaded} out of {totalStorage}', + 'title' => 'Αποθηκευτικός χώρος', + 'subtitle' => '{totalUploaded} από {totalStorage}', ], ]; diff --git a/modules/Admin/Language/el/Episode.php b/modules/Admin/Language/el/Episode.php index 53c6ea41..cf72d9e7 100644 --- a/modules/Admin/Language/el/Episode.php +++ b/modules/Admin/Language/el/Episode.php @@ -22,39 +22,41 @@ return [ 'all_podcast_episodes' => 'Όλα τα επεισόδια του podcast', 'back_to_podcast' => 'Μετάβαση πίσω στο podcast', 'edit' => 'Επεξεργασία', + 'preview' => 'Preview', 'publish' => 'Δημοσίευση', 'publish_edit' => 'Επεξεργασία δημοσίευσης', - 'publish_date_edit' => 'Edit publication date', + 'publish_date_edit' => 'Επεξεργασία ημερομηνίας δημοσίευσης', 'unpublish' => 'Αναίρεση δημοσίευσης', 'publish_error' => 'Το επεισόδιο έχει ήδη δημοσιευθεί.', 'publish_edit_error' => 'Το επεισόδιο έχει ήδη δημοσιευθεί.', 'publish_cancel_error' => 'Το επεισόδιο έχει ήδη δημοσιευθεί.', - 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', - 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', - 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'publish_date_edit_error' => 'Το επεισόδιο δεν έχει δημοσιευθεί ακόμα, δεν μπορείτε να επεξεργαστείτε την ημερομηνία έκδοσής του.', + 'publish_date_edit_future_error' => 'Η ημερομηνία δημοσίευσης του επεισοδίου μπορεί να οριστεί μόνο σε μια προηγούμενη ημερομηνία! Αν θέλετε να την προγραμματίσετε εκ νέου, αποδημοσιεύστε την πρώτα.', + 'publish_date_edit_success' => 'Η ημερομηνία δημοσίευσης του επεισοδίου έχει ενημερωθεί με επιτυχία!', 'unpublish_error' => 'Το επεισόδιο δεν έχει δημοσιευθεί.', 'delete' => 'Διαγραφή', 'go_to_page' => 'Μετάβαση στη σελίδα', 'create' => 'Προσθήκη επεισοδίου', 'publication_status' => [ 'published' => 'Δημοσιευμένο', - 'with_podcast' => 'Published', + 'with_podcast' => 'Δημοσιευμένο', 'scheduled' => 'Προγραμματισμένο', 'not_published' => 'Δεν έχει δημοσιευτεί', ], - 'with_podcast_hint' => 'To be published at the same time as the podcast', + 'with_podcast_hint' => 'Να δημοσιευτεί ταυτόχρονα με το podcast', 'list' => [ 'search' => [ - 'placeholder' => 'Search for an episode', - 'clear' => 'Clear search', - 'submit' => 'Search', + 'placeholder' => 'Αναζήτηση ενός επεισοδίου', + 'clear' => 'Καθαρισμός αναζήτησης', + 'submit' => 'Αναζήτηση', ], 'number_of_episodes' => '{numberOfEpisodes, plural, - one {# episode} - other {# episodes} + one {# επισόδειο} + other {# επισόδεια} }', 'episode' => 'Επεισόδιο', 'visibility' => 'Ορατότητα', + 'downloads' => 'Downloads', 'comments' => 'Σχόλια', 'actions' => 'Ενέργειες', ], @@ -62,22 +64,22 @@ return [ 'createSuccess' => 'Το επεισόδιο δημιουργήθηκε με επιτυχία!', 'editSuccess' => 'Το επεισόδιο ενημερώθηκε με επιτυχία!', 'publishSuccess' => '{publication_status, select, - published {Episode successfully published!} - scheduled {Episode publication successfully scheduled!} - with_podcast {This episode will be published at the same time as the podcast.} - other {This episode is not published.} + published {Το επεισόδιο δημοσιεύτηκε με επιτυχία!} + scheduled {Η έκδοση για το επεισόδιο έχει προγραμματιστεί επιτυχώς!} + with_podcast {Αυτό το επεισόδιο θα δημοσιευθεί ταυτόχρονα με το podcast.} + other {Αυτό το επεισόδιο δεν έχει δημοσιευθεί.} }', 'publishCancelSuccess' => 'Η δημοσίευση του επεισοδίου ακυρώθηκε επιτυχώς!', - 'unpublishBeforeDeleteTip' => 'You must unpublish the episode before deleting it.', - 'scheduleDateError' => 'Schedule date must be set!', - 'deletePublishedEpisodeError' => 'Please unpublish the episode before deleting it.', - 'deleteSuccess' => 'Episode successfully deleted!', - 'deleteError' => 'Failed to delete episode {type, select, + 'unpublishBeforeDeleteTip' => 'Πρέπει να καταργήσετε τη δημοσίευση του επεισοδίου πριν τη διαγραφή.', + 'scheduleDateError' => 'Η ημερομηνία πρέπει να οριστεί!', + 'deletePublishedEpisodeError' => 'Πρέπει να καταργήσετε τη δημοσίευση του επεισοδίου πριν τη διαγραφή.', + 'deleteSuccess' => 'Το επεισόδιο διαγράφτηκε με επιτυχία!', + 'deleteError' => 'Αποτυχία διαγραφής επεισοδίου {type, select, transcript {transcript} - chapters {chapters} - image {cover} - audio {audio} - other {media} + chapters {κεφάλαια} + image {καλύπτουν} + audio {ήχος} + other {πολυμέσα} }.', 'deleteFileError' => 'Failed to delete {type, select, transcript {transcript} @@ -85,8 +87,8 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', - 'sameSlugError' => 'An episode with the chosen slug already exists.', + } file {file_key}. You may manually remove it from your disk.', + 'sameSlugError' => 'Ένα επεισόδιο με το επιλεγμένο slug υπάρχει ήδη.', ], 'form' => [ 'file_size_error' => @@ -97,7 +99,7 @@ return [ 'cover' => 'Εξώφυλλο επισοδίου', 'cover_hint' => 'Εάν δεν ορίσετε ένα εξώφυλλο, το εξώφυλλο του podcast θα χρησιμοποιηθεί αντ \'αυτού.', - 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'cover_size_hint' => 'Το εξώφυλλο πρέπει να είναι τουλάχιστον 1400px πλάτος και ύψος.', 'title' => 'Τίτλος', 'title_hint' => 'Θα πρέπει να υπάρχει ένα σαφές και συνοπτικό όνομα επεισοδίου. Μην καθορίσετε εδώ το επεισόδιο ή τους αριθμούς της σεζόν.', @@ -114,7 +116,7 @@ return [ 'bonus_hint' => 'Επιπλέον περιεχόμενο για την παράσταση (για παράδειγμα, πίσω από τις σκηνές πληροφορίες ή συνεντεύξεις με τη cast) ή δια-διαφημιστικό περιεχόμενο για μια άλλη παράσταση', ], 'premium_title' => 'Premium', - 'premium' => 'Episode must be accessible to premium subscribers only', + 'premium' => 'Το επεισόδιο πρέπει να είναι προσβάσιμο μόνο σε συνδρομητές premium', 'parental_advisory' => [ 'label' => 'Γονικός σύμβουλος', 'hint' => 'Μήπως το επεισόδιο περιέχει ακατάλληλο περιεχόμενο;', @@ -137,9 +139,9 @@ return [ 'location_name' => 'Όνομα τοποθεσίας ή διεύθυνση', 'location_name_hint' => 'Αυτή μπορεί να είναι μια πραγματική ή φανταστική τοποθεσία', 'transcript' => 'Απομαγνητοφώνηση (υπότιτλοι / κλειστοί υπότιτλοι)', - 'transcript_hint' => 'Επιτρέπονται μόνο .srt αρχεία.', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', 'transcript_download' => 'Λήψη απομαγνητοφώνησης', - 'transcript_file' => 'Αρχείο απομαγνητοφώνησης (.srt)', + 'transcript_file' => 'Transcript file (.srt or .vtt)', 'transcript_remote_url' => 'Απομακρυσμένη διεύθυνση url για απομαγνητοφώνηση', 'transcript_file_delete' => 'Διαγραφή αρχείου απομαγνητοφώνησης', 'chapters' => 'Κεφάλαια', @@ -153,9 +155,9 @@ return [ 'Αν χρειάζεστε ετικέτες RSS που δεν χειρίζεται το Castopod, ορίστε τις εδώ.', 'custom_rss' => 'Προσαρμοσμένες ετικέτες RSS για το επεισόδιο', 'custom_rss_hint' => 'Αυτό θα ενεθεί εντός της ετικέτας "item".', - 'block' => 'Episode should be hidden from public catalogues', + 'block' => 'Το επεισόδιο πρέπει να είναι κρυμμένο από όλες τις πλατφόρμες', 'block_hint' => - 'The episode show or hide status: toggling this on prevents the episode from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'H κατάσταση εμφάνιση ή απόκρυψη επισοδείου: Η εναλλαγή αποτρέπει την εμφάνιση του επεισοδίου στο Apple Podcast Google Podcasts, και σε οποιεσδήποτε εφαρμογή τρίτων που τραβούν τις εμφανίσεις από αυτούς τους καταλόγους. (Μη εγγυημένη)', 'submit_create' => 'Δημιουργία επεισοδίου', 'submit_edit' => 'Αποθήκευση επεισοδίου', ], @@ -169,7 +171,7 @@ return [ 'publication_method' => [ 'now' => 'Τώρα', 'schedule' => 'Προγραμματισμός', - 'with_podcast' => 'Publish alongside podcast', + 'with_podcast' => 'Δημοσίευση παράλληλα με podcast', ], 'scheduled_publication_date' => 'Ημερομηνία προγραμματισμένης δημοσίευσης', 'scheduled_publication_date_clear' => 'Εκκαθάριση ημερομηνίας δημοσίευσης', @@ -183,21 +185,21 @@ return [ 'message_warning_submit' => 'Δημοσίευση ούτως ή άλλως', ], 'publish_date_edit_form' => [ - 'new_publication_date' => 'New publication date', - 'new_publication_date_hint' => 'Must be set to a past date.', - 'submit' => 'Edit publication date', + 'new_publication_date' => 'Ημερομηνία νέας δημοσίευσης', + 'new_publication_date_hint' => 'Πρέπει να οριστεί σε μια προηγούμενη ημερομηνία.', + 'submit' => 'Επεξεργασία ημερομηνίας δημοσίευσης', ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", + "Η κατάργηση της δημοσίευσης του επεισοδίου θα διαγράψει όλα τα σχόλια και τις δημοσιεύσεις που σχετίζονται με αυτό και θα τα αφαιρέσει από τη ροή RSS του podcast.", 'understand' => 'Καταλαβαίνω, θέλω να αποδημοσιεύσει το επεισόδιο', 'submit' => 'Αναίρεση δημοσίευσης', ], 'delete_form' => [ 'disclaimer' => - "Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.", + "Διαγράφοντας το επεισόδιο θα διαγράψετε όλα τα αρχεία πολυμέσων, τα σχόλια, τα βίντεο κλιπ και τα ηχητικά δεδομένα που σχετίζονται με αυτό.", 'understand' => 'Καταλαβαίνω, θέλω να διαγράψω το επεισόδιο', - 'submit' => 'Delete', + 'submit' => 'Διαγραφή', ], 'embed' => [ 'title' => 'Ενσωματώσιμος αναπαραγωγέας', @@ -210,4 +212,14 @@ return [ 'light' => 'Ανοιχτόχρωμο', 'light-transparent' => 'Ανοιχτό διαφανές', ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], ]; diff --git a/modules/Admin/Language/el/Navigation.php b/modules/Admin/Language/el/Navigation.php index 2325bd38..8d766a63 100644 --- a/modules/Admin/Language/el/Navigation.php +++ b/modules/Admin/Language/el/Navigation.php @@ -12,12 +12,14 @@ return [ 'toggle_sidebar' => 'Εναλλαγή πλαϊνής μπάρας', 'go_to_website' => 'Μεταβείτε στον ιστότοπο', 'go_to_admin' => 'Μεταβείτε στον πίνακα διαχείρισης', + 'not-authorized' => 'Not authorized', 'dashboard' => 'Πίνακας εργαλείων', 'admin' => 'Αρχική σελίδα', 'podcasts' => 'Podcasts', 'podcast-list' => 'Όλα τα podcasts', 'podcast-create' => 'Νέο podcast', - 'podcast-import' => 'Εισαγωγή ενός podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', 'persons' => 'Πρόσωπα', 'person-list' => 'Όλα τα άτομα', 'person-create' => 'Νέο άτομο', @@ -33,6 +35,7 @@ return [ 'settings' => 'Ρυθμίσεις', 'settings-general' => 'Γενικά', 'settings-theme' => 'Θέμα', + 'admin-about' => 'About', 'account' => [ 'my-account' => 'Ο λογαριασμός μου', 'change-password' => 'Αλλαγή κωδικού πρόσβασης', diff --git a/modules/Admin/Language/el/Notifications.php b/modules/Admin/Language/el/Notifications.php index 2b139d51..3e87ddba 100644 --- a/modules/Admin/Language/el/Notifications.php +++ b/modules/Admin/Language/el/Notifications.php @@ -9,11 +9,11 @@ declare(strict_types=1); */ return [ - 'title' => 'Notifications', - 'reply' => '{actor_username} replied to your post', - 'favourite' => '{actor_username} favourited your post', - 'reblog' => '{actor_username} shared your post', - 'follow' => '{actor_username} started following you', - 'no_notifications' => 'No notifications', - 'mark_all_as_read' => 'Mark all as read', + 'title' => 'Ειδοποιήσεις', + 'reply' => '{actor_username} απάντησε στο post σας', + 'favourite' => '{actor_username} έβαλε αγαπημένη τη δημοσίευσή σας', + 'reblog' => '{actor_username} κοινοποίησε τη δημοσίευσή σας', + 'follow' => '{actor_username} ξεκίνησε να σας ακολουθεί', + 'no_notifications' => 'Καμία ειδοποίηση', + 'mark_all_as_read' => 'Σήμανση όλων ως αναγνωσμένα', ]; diff --git a/modules/Admin/Language/el/Platforms.php b/modules/Admin/Language/el/Platforms.php index 4b5f2fb5..2c5a10ec 100644 --- a/modules/Admin/Language/el/Platforms.php +++ b/modules/Admin/Language/el/Platforms.php @@ -9,9 +9,22 @@ declare(strict_types=1); */ return [ - 'title' => 'Πλατφόρμες', + 'title' => [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', 'home_url' => 'Μεταβείτε στην ιστοσελίδα {platformName}', + 'register' => 'Register', 'submit_url' => 'Υποβάλετε το podcast σας στην πλατφόρμα {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', 'visible' => 'Εμφάνιση στην αρχική σελίδα του podcast;', 'on_embed' => 'Εμφάνιση σε ενσωματωμένο player;', 'remove' => 'Κατάργηση {platformName}', diff --git a/modules/Admin/Language/el/Podcast.php b/modules/Admin/Language/el/Podcast.php index a0f061b4..d24fa87c 100644 --- a/modules/Admin/Language/el/Podcast.php +++ b/modules/Admin/Language/el/Podcast.php @@ -13,116 +13,137 @@ return [ 'no_podcast' => 'Δεν βρέθηκαν podcast!', 'create' => 'Δημιουργία podcast', 'import' => 'Εισαγωγή ενός podcast', + 'all_imports' => 'Podcast imports', 'new_episode' => 'Νέο Επεισόδιο', 'view' => 'Προβολή podcast', 'edit' => 'Επεξεργασία podcast', - 'publish' => 'Publish podcast', - 'publish_edit' => 'Edit publication', + 'publish' => 'Δημοσίευση podcast', + 'publish_edit' => 'Επεξεργασία δημοσίευσης', 'delete' => 'Διαγραφή podcast', 'see_episodes' => 'Δείτε επεισόδια', 'see_contributors' => 'Βλέπε συντελεστές', + 'monetization_other' => 'Other monetization', 'go_to_page' => 'Μετάβαση στη σελίδα', 'latest_episodes' => 'Τελευταία επεισόδια', 'see_all_episodes' => 'Δείτε όλα τα επεισόδια', - 'draft' => 'Draft', + 'draft' => 'Πρόχειρο', 'messages' => [ - 'createSuccess' => 'Podcast successfully created!', + 'createSuccess' => 'Το επεισόδιο δημιουργήθηκε με επιτυχία!', 'editSuccess' => 'Το Podcast ενημερώθηκε με επιτυχία!', 'importSuccess' => 'Το Podcast εισήχθη με επιτυχία!', - 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', - 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, - cover {cover} + 'deleteSuccess' => 'Το Podcast @{podcast_handle} διαγράφηκε επιτυχώς!', + 'deletePodcastMediaError' => 'Αποτυχία διαγραφής podcast {type, select, + cover {εξώφυλλο} banner {banner} - other {media} + other {πολυμέσα} }.', - 'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select, + 'deleteEpisodeMediaError' => 'Αποτυχία διαγραφής του podcast επεισοδίου {episode_slug} {type, select, transcript {transcript} - chapters {chapters} - image {cover} - audio {audio} - other {media} + chapters {κεφάλαια} + image {κάλυμμα} + audio {ήχος} + other {πολυμέσα} }.', - 'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.', - 'podcastFeedUpdateSuccess' => 'Successful update: {number_of_new_episodes, plural, - one {# episode was} - other {# episodes were} - } added to the podcast!', - 'podcastFeedUpToDate' => 'Podcast is already up to date.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', - 'publishError' => 'This podcast is either already published or scheduled for publication.', - 'publishEditError' => 'This podcast is not scheduled for publication.', - 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', - 'scheduleDateError' => 'Schedule date must be set!', + 'deletePodcastMediaFolderError' => 'Αποτυχία διαγραφής του φακέλου μέσων podcast {folder_path}. Μπορείτε να τον αφαιρέσετε χειροκίνητα από το δίσκο σας.', + 'podcastFeedUpdateSuccess' => 'Επιτυχημένη ενημέρωση: {number_of_new_episodes, plural, + one {# επεισόδιο ήταν} + other {# επεισόδια ήταν} + } προστέθηκαν στο podcast!', + 'podcastFeedUpToDate' => 'Το Podcast είναι ήδη ενημερωμένο.', + 'publishError' => 'Αυτό το podcast είτε έχει ήδη δημοσιευθεί είτε έχει προγραμματιστεί για δημοσίευση.', + 'publishEditError' => 'Αυτό το podcast δεν έχει προγραμματιστεί για δημοσίευση.', + 'publishCancelSuccess' => 'Η δημοσίευση του επεισοδίου ακυρώθηκε επιτυχώς!', + 'scheduleDateError' => 'Η ημερομηνία πρέπει να οριστεί!', ], 'form' => [ 'identity_section_title' => 'Αναγνωριστικό Podcast', 'identity_section_subtitle' => 'Αυτά τα πεδία σας επιτρέπουν να ξεχωρίσει το podcast σας.', - 'cover' => 'Podcast cover', - 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', - 'banner' => 'Podcast banner', - 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', - 'banner_delete' => 'Delete podcast banner', - 'title' => 'Title', + 'fediverse_section_title' => 'Fediverse identity', + + 'cover' => 'Εξώφυλο podcast', + 'cover_size_hint' => 'Το εξώφυλλο πρέπει να είναι τουλάχιστον 1400px πλάτος και ύψος.', + 'banner' => 'Banner podcast', + 'banner_size_hint' => 'Το Banner πρέπει να έχει αναλογία 3:1 και να είναι τουλάχιστον 1500px πλάτος.', + 'banner_delete' => 'Διαγραφή του podcast banner', + 'title' => 'Τίτλος', 'handle' => 'Handle', 'handle_hint' => - 'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.', + 'Χρησιμοποιείται για τον προσδιορισμό του podcast. Τα κεφαλαία, τα πεζά, οι αριθμοί και οι κάτω παύλες γίνονται αποδεκτές.', 'type' => [ - 'label' => 'Type', + 'label' => 'Είδος', 'episodic' => 'Episodic', - 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', - 'serial' => 'Serial', - 'serial_hint' => 'If episodes are intended to be consumed in sequential order. The oldest episodes will be presented first.', + 'episodic_hint' => 'Εάν τα επεισόδια προορίζονται να καταναλωθούν χωρίς καμία συγκεκριμένη σειρά.', + 'serial' => 'Σειριακός Αριθμός', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', ], - 'description' => 'Description', - 'classification_section_title' => 'Classification', + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + ], + 'description' => 'Περιγραφή', + 'classification_section_title' => 'Χαρακτηρισμός', 'classification_section_subtitle' => - 'These fields will impact your audience and competition.', - 'language' => 'Language', - 'category' => 'Category', - 'category_placeholder' => 'Select a category…', - 'other_categories' => 'Other categories', + 'Αυτά τα πεδία θα επηρεάσουν το κοινό και τον ανταγωνισμό σας.', + 'language' => 'Γλώσσα', + 'category' => 'Κατηγορία', + 'category_placeholder' => 'Επιλέξτε μια κατηγορία…', + 'other_categories' => 'Άλλες κατηγορίες', 'parental_advisory' => [ - 'label' => 'Parental advisory', - 'hint' => 'Does it contain explicit content?', - 'undefined' => 'undefined', - 'clean' => 'Clean', - 'explicit' => 'Explicit', + 'label' => 'Γονικός σύμβουλος', + 'hint' => 'Μήπως το επεισόδιο περιέχει ακατάλληλο περιεχόμενο;', + 'undefined' => 'απροσδιόριστο', + 'clean' => 'Καθαρισμός', + 'explicit' => 'Άσεμνο περιεχόμενο', ], - 'author_section_title' => 'Author', - 'author_section_subtitle' => 'Who is managing the podcast?', - 'owner_name' => 'Owner name', + 'author_section_title' => 'Συντάκτης', + 'author_section_subtitle' => 'Ποιος διαχειρίζεται το podcast;', + 'owner_name' => 'Όνομα κατόχου', 'owner_name_hint' => - 'For administrative use only. Visible in the public RSS feed.', - 'owner_email' => 'Owner email', + 'Μόνο για διοικητική χρήση. Ορατό στη δημόσια τροφοδοσία RSS.', + 'owner_email' => 'Email ιδιοκτήτη', 'owner_email_hint' => - 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', - 'publisher' => 'Publisher', + 'Θα χρησιμοποιηθεί από τις περισσότερες πλατφόρμες για να επαληθεύσει την ιδιοκτησία του podcast. Ορατό στη δημόσια τροφοδοσία RSS.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', + 'publisher' => 'Εκδότης', 'publisher_hint' => - 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', - 'copyright' => 'Copyright', - 'location_section_title' => 'Location', - 'location_section_subtitle' => 'What place is this podcast about?', - 'location_name' => 'Location name or address', - 'location_name_hint' => 'This can be a real place or fictional', + 'Η ομάδα που είναι υπεύθυνη για τη δημιουργία της παράστασης. Συχνά αναφέρεται στη μητρική εταιρεία ή στο δίκτυο ενός podcast. Αυτό το πεδίο μερικές φορές χαρακτηρίζεται ως \'Συγγραφέας\'.', + 'copyright' => 'Πνευματικά δικαιώματα', + 'location_section_title' => 'Τοποθεσία', + 'location_section_subtitle' => 'Σε ποιο μέρος είναι αυτό το επεισόδιο;', + 'location_name' => 'Όνομα τοποθεσίας ή διεύθυνση', + 'location_name_hint' => 'Αυτό μπορεί να είναι ένα πραγματικό μέρος ή φανταστικό', 'monetization_section_title' => 'Monetization', 'monetization_section_subtitle' => - 'Earn money thanks to your audience.', + 'Κερδίστε χρήματα χάρη στο κοινό σας.', 'premium' => 'Premium', - 'premium_by_default' => 'Episodes must be set as premium by default', - 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', - 'payment_pointer' => 'Payment Pointer for Web Monetization', + 'premium_by_default' => 'Τα επεισόδια πρέπει να ορίζονται ως premium από προεπιλογή', + 'premium_by_default_hint' => 'Τα επεισόδια Podcast θα επισημανθούν ως premium από προεπιλογή. Μπορείτε ακόμα να επιλέξετε να ορίσετε κάποια επεισόδια, trailers ή μπόνους ως δημόσια.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', + 'payment_pointer' => 'Δείκτης πληρωμής για Monetization Web', 'payment_pointer_hint' => - 'This is your where you will receive money thanks to Web Monetization', + 'Αυτό είναι το πού θα λάβετε χρήματα χάρη στην Monetization Web', 'advanced_section_title' => 'Advanced Parameters', 'advanced_section_subtitle' => 'If you need RSS tags that Castopod does not handle, set them here.', 'custom_rss' => 'Custom RSS tags for the podcast', 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', 'new_feed_url' => 'New feed URL', 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', 'partnership' => 'Partnership', 'partner_id' => 'ID', 'partner_link_url' => 'Link URL', @@ -130,7 +151,6 @@ return [ 'partner_id_hint' => 'Your own partner ID', 'partner_link_url_hint' => 'The generic partner link address', 'partner_image_url_hint' => 'The generic partner image address', - 'status_section_title' => 'Status', 'block' => 'Podcast should be hidden from public catalogues', 'block_hint' => 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', @@ -168,56 +188,56 @@ return [ 'food' => 'Food', 'performing_arts' => 'Performing Arts', 'visual_arts' => 'Visual Arts', - 'careers' => 'Careers', - 'entrepreneurship' => 'Entrepreneurship', - 'investing' => 'Investing', - 'management' => 'Management', - 'marketing' => 'Marketing', - 'non_profit' => 'Non-Profit', - 'comedy_interviews' => 'Comedy Interviews', - 'improv' => 'Improv', + 'careers' => 'Καριέρες', + 'entrepreneurship' => 'Επιχειρηματικότητα', + 'investing' => 'Επενδύσεις', + 'management' => 'Διαχείριση', + 'marketing' => 'Μάρκετινγκ', + 'non_profit' => 'Μη κερδοσκοπικού χαρακτήρα', + 'comedy_interviews' => 'Συνεντεύξεις Κωμωδίας', + 'improv' => 'Βελτίωση', 'stand_up' => 'Stand-Up', - 'courses' => 'Courses', + 'courses' => 'Σεμινάρια', 'how_to' => 'How To', - 'language_learning' => 'Language Learning', - 'self_improvement' => 'Self-Improvement', - 'comedy_fiction' => 'Comedy Fiction', - 'drama' => 'Drama', - 'science_fiction' => 'Science Fiction', - 'alternative_health' => 'Alternative Health', - 'fitness' => 'Fitness', - 'medicine' => 'Medicine', - 'mental_health' => 'Mental Health', - 'nutrition' => 'Nutrition', - 'sexuality' => 'Sexuality', - 'education_for_kids' => 'Education for Kids', - 'parenting' => 'Parenting', - 'pets_and_animals' => 'Pets & Animals', - 'stories_for_kids' => 'Stories for Kids', + 'language_learning' => 'Εκμάθηση γλωσσών', + 'self_improvement' => 'Αυτοβελτίωση', + 'comedy_fiction' => 'Φαντασία Κωμωδίας', + 'drama' => 'Δράμα', + 'science_fiction' => 'Επιστημονικής φαντασίας', + 'alternative_health' => 'Εναλλακτική Υγεία', + 'fitness' => 'Γυμναστική', + 'medicine' => 'Ιατρική', + 'mental_health' => 'Ψυχική Υγεία', + 'nutrition' => 'Διατροφή', + 'sexuality' => 'Σεξουαλικότητα', + 'education_for_kids' => 'Εκπαίδευση για παιδιά', + 'parenting' => 'Γονείς', + 'pets_and_animals' => 'Κατοικίδια & Ζώα', + 'stories_for_kids' => 'Ιστορίες για παιδιά', 'animation_and_manga' => 'Animation & Manga', - 'automotive' => 'Automotive', - 'aviation' => 'Aviation', - 'crafts' => 'Crafts', - 'games' => 'Games', - 'hobbies' => 'Hobbies', - 'home_and_garden' => 'Home & Garden', - 'video_games' => 'Video Games', - 'music_commentary' => 'Music Commentary', - 'music_history' => 'Music History', - 'music_interviews' => 'Music Interviews', - 'business_news' => 'Business News', - 'daily_news' => 'Daily News', - 'entertainment_news' => 'Entertainment News', + 'automotive' => 'Αυτοκίνητα', + 'aviation' => 'Αεροπορία', + 'crafts' => 'Χειροτεχνίες', + 'games' => 'Παιχνίδια', + 'hobbies' => 'Χόμπι', + 'home_and_garden' => 'Σπίτι & Κήπος', + 'video_games' => 'Βιντεοπαιχνίδια', + 'music_commentary' => 'Σχολιασμός Μουσικής', + 'music_history' => 'Ιστορία Μουσικής', + 'music_interviews' => 'Συνεντεύξεις Μουσικής', + 'business_news' => 'Επιχειρηματικά Νέα', + 'daily_news' => 'Καθημερινές Ειδήσεις', + 'entertainment_news' => 'Ειδήσεις ψυχαγωγίας', 'news_commentary' => 'News Commentary', - 'politics' => 'Politics', - 'sports_news' => 'Sports News', - 'tech_news' => 'Tech News', - 'buddhism' => 'Buddhism', - 'christianity' => 'Christianity', + 'politics' => 'Πολιτική', + 'sports_news' => 'Αθλητικά νέα', + 'tech_news' => 'Ειδήσεις Τεχνολογίας', + 'buddhism' => 'Βουδισμός', + 'christianity' => 'Χριστιανισμός', 'hinduism' => 'Hinduism', - 'islam' => 'Islam', - 'judaism' => 'Judaism', - 'religion' => 'Religion', + 'islam' => 'Ισλαμισμός', + 'judaism' => 'Ιουδαϊσμός', + 'religion' => 'Θρησκεία', 'spirituality' => 'Spirituality', 'astronomy' => 'Astronomy', 'chemistry' => 'Chemistry', @@ -272,39 +292,39 @@ return [ 'submit_edit' => 'Edit publication', 'cancel_publication' => 'Cancel publication', 'message_warning' => 'You did not write a message for your announcement post!', - 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', - 'message_warning_submit' => 'Publish anyway', + 'message_warning_hint' => 'Έχοντας ένα μήνυμα αυξάνει την κοινωνική δέσμευση, με αποτέλεσμα μια καλύτερη ορατότητα για το podcast σας.', + 'message_warning_submit' => 'Δημοσίευση ούτως ή άλλως', ], 'publication_status_banner' => [ - 'draft_mode' => 'draft mode', - 'not_published' => 'This podcast is not yet published.', - 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + 'draft_mode' => 'λειτουργία πρόχειρου', + 'not_published' => 'Αυτό το podcast δεν έχει ακόμη δημοσιευθεί.', + 'scheduled' => 'Αυτό το podcast έχει προγραμματιστεί για δημοσίευση στις {publication_date}.', ], 'delete_form' => [ 'disclaimer' => - "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", - 'understand' => 'I understand, I want the podcast to be permanently deleted', - 'submit' => 'Delete', + "Διαγράφοντας το podcast θα διαγράψει όλα τα επεισόδια, τα αρχεία πολυμέσων, τις δημοσιεύσεις και τα αναλυτικά στοιχεία που σχετίζονται με αυτό. Αυτή η ενέργεια είναι μη αναστρέψιμη, δεν θα είστε σε θέση να τις ανακτήσετε αργότερα.", + 'understand' => 'Καταλαβαίνω, θέλω το podcast να διαγραφεί οριστικά', + 'submit' => 'Διαγραφή', ], - 'by' => 'By {publisher}', - 'season' => 'Season {seasonNumber}', - 'list_of_episodes_year' => '{year} episodes ({episodeCount})', + 'by' => 'Από {publisher}', + 'season' => 'Σεζόν {seasonNumber}', + 'list_of_episodes_year' => '{year} επεισόδια ({episodeCount})', 'list_of_episodes_season' => - 'Season {seasonNumber} episodes ({episodeCount})', - 'no_episode' => 'No episode found!', - 'follow' => 'Follow', + 'Σεζόν {seasonNumber} επεισόδεια ({episodeCount})', + 'no_episode' => 'Δεν βρέθηκε επεισόδιο!', + 'follow' => 'Ακολουθήστε', 'followers' => '{numberOfFollowers, plural, - one {# follower} - other {# followers} + one {# ακόλουθος} + other {# ακόλουθοι} }', 'posts' => '{numberOfPosts, plural, - one {# post} - other {# posts} + one {# δημοσίευση} + other {# δημοσιεύσεις} }', - 'activity' => 'Activity', - 'episodes' => 'Episodes', - 'sponsor' => 'Sponsor', - 'funding_links' => 'Funding links for {podcastTitle}', - 'find_on' => 'Find {podcastTitle} on', - 'listen_on' => 'Listen on', + 'activity' => 'Δραστηριότητα', + 'episodes' => 'Επεισόδια', + 'sponsor' => 'Χορηγός', + 'funding_links' => 'Σύνδεσμοι χρηματοδότησης για το {podcastTitle}', + 'find_on' => 'Βρείτε το {podcastTitle} στο', + 'listen_on' => 'Ακούστε το', ]; diff --git a/modules/Admin/Language/el/PodcastImport.php b/modules/Admin/Language/el/PodcastImport.php deleted file mode 100644 index 7c3ef67d..00000000 --- a/modules/Admin/Language/el/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'The podcast to import', - 'old_podcast_section_subtitle' => - 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', - 'imported_feed_url' => 'Feed URL', - 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', - 'new_podcast_section_title' => 'The new podcast', - 'advanced_params_section_title' => 'Advanced parameters', - 'advanced_params_section_subtitle' => - 'Keep the default values if you have no idea of what the fields are for.', - 'slug_field' => 'Field to be used to calculate episode slug', - 'description_field' => - 'Source field used for episode description / show notes', - 'force_renumber' => 'Force episodes renumbering', - 'force_renumber_hint' => - 'Use this if your podcast does not have episode numbers but wish to set them during import.', - 'season_number' => 'Season number', - 'season_number_hint' => - 'Use this if your podcast does not have a season number but wish to set one during import. Leave blank otherwise.', - 'max_episodes' => 'Maximum number of episodes to import', - 'max_episodes_hint' => 'Leave blank to import all episodes', - 'lock_import' => - 'This feed is protected. You cannot import it. If you are the owner, unprotect it on the origin platform.', - 'submit' => 'Import podcast', -]; diff --git a/modules/Admin/Language/el/PodcastNavigation.php b/modules/Admin/Language/el/PodcastNavigation.php index b4d7ddc0..d5310a9a 100644 --- a/modules/Admin/Language/el/PodcastNavigation.php +++ b/modules/Admin/Language/el/PodcastNavigation.php @@ -9,30 +9,34 @@ declare(strict_types=1); */ return [ - 'go_to_page' => 'Go to podcast page', - 'dashboard' => 'Podcast dashboard', - 'podcast-view' => 'Home', - 'podcast-edit' => 'Edit podcast', - 'podcast-persons-manage' => 'Manage persons', - 'episodes' => 'Episodes', - 'episode-list' => 'All episodes', - 'episode-create' => 'New episode', - 'analytics' => 'Analytics', - 'podcast-analytics' => 'Audience overview', - 'podcast-analytics-webpages' => 'Web pages visits', - 'podcast-analytics-locations' => 'Locations', - 'podcast-analytics-unique-listeners' => 'Unique listeners', - 'podcast-analytics-players' => 'Players', - 'podcast-analytics-listening-time' => 'Listening time', - 'podcast-analytics-time-periods' => 'Time periods', - 'premium' => 'Premium', - 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', - 'contributors' => 'Contributors', - 'contributor-list' => 'All contributors', - 'contributor-add' => 'Add contributor', - 'platforms' => 'External platforms', - 'platforms-podcasting' => 'Podcasting', - 'platforms-social' => 'Social networks', - 'platforms-funding' => 'Funding', + 'go_to_page' => 'Μετάβαση στη σελίδα podcast', + 'rss_feed' => 'RSS feed', + 'dashboard' => 'Πίνακας ελέγχου Podcast', + 'podcast-view' => 'Αρχική σελίδα', + 'podcast-edit' => 'Επεξεργασία podcast', + 'podcast-persons-manage' => 'Διαχείριση ατόμων', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', + 'episodes' => 'Επεισόδια', + 'episode-list' => 'Όλα τα επεισόδια', + 'episode-create' => 'Νέο Επεισόδιο', + 'analytics' => 'Στατιστικά', + 'podcast-analytics' => 'Επισκόπηση κοινού', + 'podcast-analytics-webpages' => 'Επίσκεψη ιστοσελίδων', + 'podcast-analytics-locations' => 'Τοποθεσίες', + 'podcast-analytics-unique-listeners' => 'Μοναδικοί ακροατές', + 'podcast-analytics-players' => 'Αναπαραγωγές', + 'podcast-analytics-listening-time' => 'Χρόνος ακρόασης', + 'podcast-analytics-time-periods' => 'Χρονικές περίοδοι', + 'monetization' => 'Monetization', + 'subscription-list' => 'Όλες οι συνδρομές', + 'subscription-create' => 'Add subscription', + 'contributors' => 'Συντελεστές', + 'contributor-list' => 'Όλοι οι συντελεστές', + 'contributor-add' => 'Προσθήκη συντελεστή', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', + 'platforms-social' => 'Κοινωνικά δίκτυα', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', ]; diff --git a/modules/Admin/Language/el/Settings.php b/modules/Admin/Language/el/Settings.php index 4a70dcba..d4ff241b 100644 --- a/modules/Admin/Language/el/Settings.php +++ b/modules/Admin/Language/el/Settings.php @@ -9,50 +9,50 @@ declare(strict_types=1); */ return [ - 'title' => 'General settings', + 'title' => 'Γενικές ρυθμίσεις', 'instance' => [ - 'title' => 'Instance', - 'site_icon' => 'Site icon', - 'site_icon_delete' => 'Delete site icon', - 'site_icon_hint' => 'Site icons are what you see on your browser tabs, bookmarks bar, and when you add a website as a shortcut on mobile devices.', - 'site_icon_helper' => 'Icon must be squared and at least 512px wide and tall.', - 'site_name' => 'Site name', - 'site_description' => 'Site description', - 'submit' => 'Save', - 'editSuccess' => 'Instance has been updated successfully!', - 'deleteIconSuccess' => 'Site icon has been remove successfully!', + 'title' => 'Διακομιστής', + 'site_icon' => 'Εικονίδιο ιστοσελίδας', + 'site_icon_delete' => 'Διαγραφή εικονιδίου ιστότοπου', + 'site_icon_hint' => 'Τα εικονίδια της ιστοσελίδας είναι αυτά που βλέπετε στις καρτέλες του προγράμματος περιήγησης, στη γραμμή σελιδοδεικτών, και όταν προσθέτετε μια ιστοσελίδα ως συντόμευση σε κινητές συσκευές.', + 'site_icon_helper' => 'Το εικονίδιο πρέπει να είναι τετράγωνο και τουλάχιστον 512px πλάτος και ψηλό.', + 'site_name' => 'Όνομα ιστοτόπου', + 'site_description' => 'Περιγραφή ιστοτόπου', + 'submit' => 'Αποθήκευση', + 'editSuccess' => 'Ο Διακομιστής έχει ενημερωθεί με επιτυχία!', + 'deleteIconSuccess' => 'Το εικονίδιο της ιστοσελίδας έχει καταργηθεί με επιτυχία!', ], 'images' => [ - 'title' => 'Images', - 'subtitle' => 'Here you can regenerate all images based on the originals that were uploaded. To be used if you find that some images are missing. This task may take a while.', - 'regenerate' => 'Regenerate images', - 'regenerationSuccess' => 'All images have been regenerated successfully!', + 'title' => 'Εικόνες', + 'subtitle' => 'Εδώ μπορείτε να επαναδημιουργήσετε όλες τις εικόνες με βάση τα πρωτότυπα που φορτώθηκαν. Για να χρησιμοποιηθεί αν βρείτε κάποιες εικόνες που λείπουν. Αυτή η εργασία μπορεί να διαρκέσει λίγο.', + 'regenerate' => 'Αναδημιουργία εικόνων', + 'regenerationSuccess' => 'Όλες οι εικόνες έχουν δημιουργηθεί επιτυχώς!', ], 'housekeeping' => [ 'title' => 'Housekeeping', 'subtitle' => 'Runs various housekeeping tasks. Use this feature if you ever encounter issues with media files or data integrity. These tasks may take a while.', - 'reset_counts' => 'Reset counts', - 'reset_counts_helper' => 'This option will recalculate and reset all data counts (number of followers, posts, comments, …).', - 'rewrite_media' => 'Rewrite media metadata', - 'rewrite_media_helper' => 'This option will delete all superfluous media files and recreate them (images, audio files, transcripts, chapters, …)', - 'rename_episodes_files' => 'Rename episode audio files', - 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', - 'clear_cache' => 'Clear all cache', - 'clear_cache_helper' => 'This option will flush redis cache or writable/cache files.', + 'reset_counts' => 'Επαναφορά μετρήσεων', + 'reset_counts_helper' => 'Αυτή η επιλογή θα επαναϋπολογίσει και θα επαναφέρει όλους τους αριθμούς δεδομένων (αριθμός των ακολούθων, αναρτήσεις, σχόλια, …).', + 'rewrite_media' => 'Επανεγγραφή μεταδεδομένων πολυμέσων', + 'rewrite_media_helper' => 'Αυτή η επιλογή θα διαγράψει όλα τα περιττά αρχεία πολυμέσων και θα τα αναπαράγει (εικόνες, αρχεία ήχου, μεταγραφές, κεφάλαια, …)', + 'rename_episodes_files' => 'Μετονομασία αρχείων ήχου επεισοδίου', + 'rename_episodes_files_hint' => 'Αυτή η επιλογή θα μετονομάσει όλα τα αρχεία ήχου επεισόδια σε μια τυχαία συμβολοσειρά χαρακτήρων. Χρησιμοποιήστε αυτό αν διαρρεύσει ένας από τους ιδιωτικούς συνδέσμους επεισοδίων σας, καθώς αυτό θα τον αποκρύψει αποτελεσματικά.', + 'clear_cache' => 'Εκκαθάριση συνολικού cache', + 'clear_cache_helper' => 'Αυτή η επιλογή θα εκκαθαρίσει αρχεία cache redis ή εγγράψιμο/cache.', 'run' => 'Run housekeeping', 'runSuccess' => 'Housekeeping has been run successfully!', ], 'theme' => [ - 'title' => 'Theme', - 'accent_section_title' => 'Accent color', - 'accent_section_subtitle' => 'Choose the color to determine the look and feel of all public pages.', - 'pine' => 'Pine', - 'crimson' => 'Crimson', - 'amber' => 'Amber', - 'lake' => 'Lake', + 'title' => 'Θέμα', + 'accent_section_title' => 'Χρώμα έμφασης', + 'accent_section_subtitle' => 'Επιλέξτε το χρώμα για να καθορίσετε την εμφάνιση και την αίσθηση όλων των δημόσιων σελίδων.', + 'pine' => 'Πεύκο', + 'crimson' => 'Βυσσινί', + 'amber' => 'Κεχριμπάρι', + 'lake' => 'Λίμνη', 'jacaranda' => 'Jacaranda', - 'onyx' => 'Onyx', - 'submit' => 'Save', - 'setInstanceThemeSuccess' => 'Theme has been updated successfully!', + 'onyx' => 'Όνυξ', + 'submit' => 'Αποθήκευση', + 'setInstanceThemeSuccess' => 'Το θέμα έχει ενημερωθεί με επιτυχία!', ], ]; diff --git a/modules/Admin/Language/el/Soundbite.php b/modules/Admin/Language/el/Soundbite.php index a3f828fe..122bdb1d 100644 --- a/modules/Admin/Language/el/Soundbite.php +++ b/modules/Admin/Language/el/Soundbite.php @@ -10,22 +10,22 @@ declare(strict_types=1); return [ 'list' => [ - 'title' => 'Soundbites', - 'soundbite' => 'Soundbite', + 'title' => 'Ήχοι', + 'soundbite' => 'Ήχος', ], 'messages' => [ - 'createSuccess' => 'Soundbite has been successfully created!', - 'deleteSuccess' => 'Soundbite has been successfully removed!', + 'createSuccess' => 'Το Soundbite δημιουργήθηκε με επιτυχία!', + 'deleteSuccess' => 'Το Soundbite αφαιρέθηκε με επιτυχία!', ], 'form' => [ - 'title' => 'New soundbite', - 'soundbite_title' => 'Soundbite title', - 'start_time' => 'Start at', - 'duration' => 'Duration', - 'submit' => 'Create soundbite', + 'title' => 'Νέο soundbite', + 'soundbite_title' => 'Τίτλος Soundbite', + 'start_time' => 'Έναρξη από', + 'duration' => 'Διάρκεια', + 'submit' => 'Δημιουργία soundbite', ], - 'play' => 'Play soundbite', - 'stop' => 'Stop soundbite', - 'create' => 'New soundbite', - 'delete' => 'Delete soundbite', + 'play' => 'Αναπαραγωγή soundbite', + 'stop' => 'Σταμάτημα soundbite', + 'create' => 'Νέο soundbite', + 'delete' => 'Διαγραφή soundbite', ]; diff --git a/modules/Admin/Language/el/Validation.php b/modules/Admin/Language/el/Validation.php index 750b1968..2c5bcf7c 100644 --- a/modules/Admin/Language/el/Validation.php +++ b/modules/Admin/Language/el/Validation.php @@ -10,9 +10,8 @@ declare(strict_types=1); return [ 'min_dims' => - '{field} is either not an image, or it is not wide or tall enough.', + '{field} είτε δεν είναι μια εικόνα, είτε δεν είναι αρκετά ευρεία ή ψηλή.', 'is_image_ratio' => - '{field} is either not an image or not of the right ratio.', - 'validate_url' => - 'The {field} field must be a valid URL (eg. https://example.com/).', + '{field} είτε δεν είναι εικόνα είτε όχι της σωστής αναλογίας.', + 'is_json' => '{field} contains invalid JSON.', ]; diff --git a/modules/Admin/Language/el/VideoClip.php b/modules/Admin/Language/el/VideoClip.php index 638de697..64388b13 100644 --- a/modules/Admin/Language/el/VideoClip.php +++ b/modules/Admin/Language/el/VideoClip.php @@ -10,63 +10,63 @@ declare(strict_types=1); return [ 'list' => [ - 'title' => 'Video clips', + 'title' => 'Βίντεο κλιπ', 'status' => [ - 'label' => 'Status', - 'queued' => 'queued', - 'queued_hint' => 'Clip is waiting to be processed.', - 'pending' => 'pending', - 'pending_hint' => 'Clip will be generated shortly.', - 'running' => 'running', - 'running_hint' => 'Clip is being generated.', - 'failed' => 'failed', - 'failed_hint' => 'Clip could not be generated: script failure.', + 'label' => 'Κατάσταση', + 'queued' => 'στην ουρά', + 'queued_hint' => 'Το κλιπ περιμένει να υποβληθεί σε επεξεργασία.', + 'pending' => 'εκκρεμεί', + 'pending_hint' => 'Το κλιπ θα δημιουργηθεί σύντομα.', + 'running' => 'εκτελείται', + 'running_hint' => 'Το κλιπ δημιουργείται.', + 'failed' => 'απέτυχε', + 'failed_hint' => 'Το κλιπ δεν μπόρεσε να δημιουργηθεί: αποτυχία δέσμης ενεργειών.', 'passed' => 'passed', - 'passed_hint' => 'Clip was generated successfully!', + 'passed_hint' => 'Το κλιπ δημιουργήθηκε με επιτυχία!', ], - 'clip' => 'Clip', - 'duration' => 'Job duration', + 'clip' => 'Αποσπάσματα', + 'duration' => 'Διάρκεια εργασίας', ], - 'title' => 'Video clip: {videoClipLabel}', - 'download_clip' => 'Download clip', - 'create' => 'New video clip', + 'title' => 'Βίντεο κλιπ: {videoClipLabel}', + 'download_clip' => 'Κατεβάστε το κλιπ', + 'create' => 'Νέο βίντεο κλιπ', 'go_to_page' => 'Go to clip page', 'retry' => 'Retry clip generation', - 'delete' => 'Delete clip', - 'logs' => 'Job logs', + 'delete' => 'Διαγραφή κλιπ', + 'logs' => 'Αρχεία καταγραφής εργασίας', 'messages' => [ - 'alreadyExistingError' => 'The video clip you are trying to create already exists!', - 'addToQueueSuccess' => 'Video clip has been added to queue, awaiting to be created!', - 'deleteSuccess' => 'Video clip has been successfully removed!', + 'alreadyExistingError' => 'Το βίντεο κλιπ που προσπαθείτε να δημιουργήσετε υπάρχει ήδη!', + 'addToQueueSuccess' => 'Το βίντεο κλιπ έχει προστεθεί στην ουρά αναμονής, αναμένοντας να δημιουργηθεί!', + 'deleteSuccess' => 'Το βίντεο κλιπ αφαιρέθηκε με επιτυχία!', ], 'format' => [ - 'landscape' => 'Landscape', - 'portrait' => 'Portrait', - 'squared' => 'Squared', + 'landscape' => 'Οριζόντια', + 'portrait' => 'Κατακόρυφα', + 'squared' => 'Τετράγωνα', ], 'form' => [ - 'title' => 'New video clip', - 'params_section_title' => 'Video clip parameters', - 'clip_title' => 'Clip title', + 'title' => 'Νέο βίντεο κλιπ', + 'params_section_title' => 'Παράμετροι βίντεο κλιπ', + 'clip_title' => 'Τίτλος κλιπ', 'format' => [ - 'label' => 'Choose a format', - 'landscape_hint' => 'With a 16:9 ratio, landscape videos are great for PeerTube, Youtube and Vimeo.', - 'portrait_hint' => 'With a 9:16 ratio, portrait videos are great for TikTok, Youtube shorts and Instagram stories.', - 'squared_hint' => 'With a 1:1 ratio, squared videos are great for Mastodon, Facebook, Twitter and LinkedIn.', + 'label' => 'Επιλογή μορφής', + 'landscape_hint' => 'Με αναλογία 16:9, τα βίντεο τοπίου είναι υπέροχα για το PeerTube, το Youtube και το Vimeo.', + 'portrait_hint' => 'Με αναλογία 9:16, πορτρέτο βίντεο είναι μεγάλη για TikTok, shorts Youtube και ιστορίες Instagram.', + 'squared_hint' => 'Με αναλογία 1:1, τα τετράγωνα βίντεο είναι υπέροχα για Mastodon, Facebook, Twitter και LinkedIn.', ], - 'theme' => 'Select a theme', - 'start_time' => 'Start at', - 'duration' => 'Duration', - 'trim_start' => 'Trim start', - 'trim_end' => 'Trim end', - 'submit' => 'Create video clip', + 'theme' => 'Επιλογή θέματος', + 'start_time' => 'Έναρξη από', + 'duration' => 'Διάρκεια', + 'trim_start' => 'Περικοπή έναρξης', + 'trim_end' => 'Περικοπή τέλους', + 'submit' => 'Δημιουργία βίντεο κλιπ', ], 'requirements' => [ - 'title' => 'Missing requirements', - 'missing' => 'You have missing requirements. Make sure to add all the required items to be allowed creating a video for this episode!', + 'title' => 'Λείπουν προαπαιτούμενα', + 'missing' => 'Έχετε απαιτήσεις που λείπουν. Σιγουρευτείτε ότι προσθέστε όλα τα απαιτούμενα στοιχεία για να μπορέσετε να δημιουργήσετε ένα βίντεο για αυτό το επεισόδιο!', 'ffmpeg' => 'FFmpeg', - 'gd' => 'Graphics Draw (GD)', - 'freetype' => 'Freetype library for GD', - 'transcript' => 'Transcript file (.srt)', + 'gd' => 'Σχεδίαση Γραφικών (GD)', + 'freetype' => 'Βιβλιοθήκη Freetype για GD', + 'transcript' => 'Αρχείο απομαγνητοφώνησης (.srt)', ], ]; diff --git a/modules/Admin/Language/en/AboutCastopod.php b/modules/Admin/Language/en/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/en/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/en/Breadcrumb.php b/modules/Admin/Language/en/Breadcrumb.php index f3269bfa..fedd6b19 100644 --- a/modules/Admin/Language/en/Breadcrumb.php +++ b/modules/Admin/Language/en/Breadcrumb.php @@ -19,24 +19,30 @@ return [ 'pages' => 'pages', 'settings' => 'settings', 'theme' => 'theme', + 'about' => 'about', 'add' => 'add', 'new' => 'new', 'edit' => 'edit', + 'plugins' => 'plugins', 'persons' => 'persons', 'publish' => 'publish', 'publish-edit' => 'edit publication', 'publish-date-edit' => 'edit publication date', 'unpublish' => 'unpublish', 'delete' => 'delete', + 'remove' => 'remove', 'fediverse' => 'fediverse', - 'block-lists' => 'block lists', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', 'users' => 'users', 'my-account' => 'my account', 'change-password' => 'change password', - 'import' => 'feed import', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', 'platforms' => 'platforms', 'social' => 'social networks', 'funding' => 'funding', + 'monetization-other' => 'other monetization', 'analytics' => 'analytics', 'locations' => 'locations', 'webpages' => 'web pages', diff --git a/modules/Admin/Language/en/Charts.php b/modules/Admin/Language/en/Charts.php index 4b33530e..6ede2510 100644 --- a/modules/Admin/Language/en/Charts.php +++ b/modules/Admin/Language/en/Charts.php @@ -37,4 +37,5 @@ return [ 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', 'total_storage_by_month' => 'Monthly storage (in MB)', 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', ]; diff --git a/modules/Admin/Language/en/Common.php b/modules/Admin/Language/en/Common.php index 596c8bcd..4dff9dd4 100644 --- a/modules/Admin/Language/en/Common.php +++ b/modules/Admin/Language/en/Common.php @@ -38,8 +38,13 @@ return [ 'noChoicesText' => 'No choices to choose from', 'maxItemText' => 'Cannot add more items', ], + 'fieldArray' => [ + 'add' => 'Add', + 'remove' => 'Remove', + ], 'upload_file' => 'Upload a file', 'remote_url' => 'Remote URL', + 'save' => 'Save', ], 'play_episode_button' => [ 'play' => 'Play', diff --git a/modules/Admin/Language/en/Episode.php b/modules/Admin/Language/en/Episode.php index 91313a7c..f7eb1290 100644 --- a/modules/Admin/Language/en/Episode.php +++ b/modules/Admin/Language/en/Episode.php @@ -22,6 +22,7 @@ return [ 'all_podcast_episodes' => 'All podcast episodes', 'back_to_podcast' => 'Go back to podcast', 'edit' => 'Edit', + 'preview' => 'Preview', 'publish' => 'Publish', 'publish_edit' => 'Edit publication', 'publish_date_edit' => 'Edit publication date', @@ -55,6 +56,7 @@ return [ }', 'episode' => 'Episode', 'visibility' => 'Visibility', + 'downloads' => 'Downloads', 'comments' => 'Comments', 'actions' => 'Actions', ], @@ -85,7 +87,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ @@ -107,11 +109,11 @@ return [ 'type' => [ 'label' => 'Type', 'full' => 'Full', - 'full_hint' => 'Complete content (the episode)', + 'full_description' => 'Complete content (the episode)', 'trailer' => 'Trailer', - 'trailer_hint' => 'Short, promotional piece of content that represents a preview of the current show', + 'trailer_description' => 'Short, promotional piece of content that represents a preview of the current show', 'bonus' => 'Bonus', - 'bonus_hint' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show', + 'bonus_description' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show', ], 'premium_title' => 'Premium', 'premium' => 'Episode must be accessible to premium subscribers only', @@ -137,9 +139,9 @@ return [ 'location_name' => 'Location name or address', 'location_name_hint' => 'This can be a real or fictional location', 'transcript' => 'Transcript (subtitles / closed captions)', - 'transcript_hint' => 'Only .srt are allowed.', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', 'transcript_download' => 'Download transcript', - 'transcript_file' => 'Transcript file (.srt)', + 'transcript_file' => 'Transcript file (.srt or .vtt)', 'transcript_remote_url' => 'Remote url for transcript', 'transcript_file_delete' => 'Delete transcript file', 'chapters' => 'Chapters', @@ -210,4 +212,14 @@ return [ 'light' => 'Light', 'light-transparent' => 'Light transparent', ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], ]; diff --git a/modules/Admin/Language/en/EpisodeNavigation.php b/modules/Admin/Language/en/EpisodeNavigation.php index 1406e301..ef3cdec0 100644 --- a/modules/Admin/Language/en/EpisodeNavigation.php +++ b/modules/Admin/Language/en/EpisodeNavigation.php @@ -20,4 +20,5 @@ return [ 'video-clips-create' => 'New video clip', 'soundbites-list' => 'Soundbites', 'soundbites-create' => 'New soundbite', + 'plugins' => 'Plugins', ]; diff --git a/modules/Admin/Language/en/Navigation.php b/modules/Admin/Language/en/Navigation.php index 68d4609d..fc7cfc30 100644 --- a/modules/Admin/Language/en/Navigation.php +++ b/modules/Admin/Language/en/Navigation.php @@ -12,12 +12,16 @@ return [ 'toggle_sidebar' => 'Toggle sidebar', 'go_to_website' => 'Go to website', 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', 'dashboard' => 'Dashboard', 'admin' => 'Home', 'podcasts' => 'Podcasts', 'podcast-list' => 'All podcasts', 'podcast-create' => 'New podcast', - 'podcast-import' => 'Import a podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', + 'plugins' => 'Plugins', + 'plugins-installed' => 'Installed', 'persons' => 'Persons', 'person-list' => 'All persons', 'person-create' => 'New person', @@ -33,6 +37,7 @@ return [ 'settings' => 'Settings', 'settings-general' => 'General', 'settings-theme' => 'Theme', + 'admin-about' => 'About', 'account' => [ 'my-account' => 'My account', 'change-password' => 'Change password', diff --git a/modules/Admin/Language/en/Platforms.php b/modules/Admin/Language/en/Platforms.php index ab17d599..e161181c 100644 --- a/modules/Admin/Language/en/Platforms.php +++ b/modules/Admin/Language/en/Platforms.php @@ -9,9 +9,22 @@ declare(strict_types=1); */ return [ - 'title' => 'Platforms', + 'title' => [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', 'visible' => 'Display in podcast homepage?', 'on_embed' => 'Display on embeddable player?', 'remove' => 'Remove {platformName}', diff --git a/modules/Admin/Language/en/Podcast.php b/modules/Admin/Language/en/Podcast.php index 426b763b..89fa9491 100644 --- a/modules/Admin/Language/en/Podcast.php +++ b/modules/Admin/Language/en/Podcast.php @@ -13,6 +13,7 @@ return [ 'no_podcast' => 'No podcast found!', 'create' => 'Create podcast', 'import' => 'Import podcast', + 'all_imports' => 'Podcast imports', 'new_episode' => 'New Episode', 'view' => 'View podcast', 'edit' => 'Edit podcast', @@ -21,6 +22,7 @@ return [ 'delete' => 'Delete podcast', 'see_episodes' => 'See episodes', 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', 'go_to_page' => 'Go to page', 'latest_episodes' => 'Latest episodes', 'see_all_episodes' => 'See all episodes', @@ -48,7 +50,6 @@ return [ other {# episodes were} } added to the podcast!', 'podcastFeedUpToDate' => 'Podcast is already up to date.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', 'publishError' => 'This podcast is either already published or scheduled for publication.', 'publishEditError' => 'This podcast is not scheduled for publication.', 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', @@ -57,6 +58,8 @@ return [ 'form' => [ 'identity_section_title' => 'Podcast identity', 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + 'cover' => 'Podcast cover', 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', 'banner' => 'Podcast banner', @@ -69,9 +72,19 @@ return [ 'type' => [ 'label' => 'Type', 'episodic' => 'Episodic', - 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', + 'episodic_description' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', 'serial' => 'Serial', - 'serial_hint' => 'If episodes are intended to be consumed in sequential order. The oldest episodes will be presented first.', + 'serial_description' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_description' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_description' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_description' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', ], 'description' => 'Description', 'classification_section_title' => 'Classification', @@ -96,6 +109,8 @@ return [ 'owner_email' => 'Owner email', 'owner_email_hint' => 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', 'publisher' => 'Publisher', 'publisher_hint' => 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', @@ -110,6 +125,11 @@ return [ 'premium' => 'Premium', 'premium_by_default' => 'Episodes must be set as premium by default', 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', 'payment_pointer' => 'Payment Pointer for Web Monetization', 'payment_pointer_hint' => 'This is your where you will receive money thanks to Web Monetization', @@ -118,11 +138,14 @@ return [ 'If you need RSS tags that Castopod does not handle, set them here.', 'custom_rss' => 'Custom RSS tags for the podcast', 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', 'new_feed_url' => 'New feed URL', 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', + 'redirect_to_new_feed' => 'Automatically redirect to new feed (301 redirect)', + 'redirect_to_new_feed_hint' => 'Check this when migrating your Castopod RSS feed to the new feed url set above. To ensure followers receive your most recent episodes from the new feed URL, maintain this redirect and the tag in your new feed for at least four weeks.', 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', 'partnership' => 'Partnership', 'partner_id' => 'ID', 'partner_link_url' => 'Link URL', @@ -130,7 +153,6 @@ return [ 'partner_id_hint' => 'Your own partner ID', 'partner_link_url_hint' => 'The generic partner link address', 'partner_image_url_hint' => 'The generic partner image address', - 'status_section_title' => 'Status', 'block' => 'Podcast should be hidden from public catalogues', 'block_hint' => 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', diff --git a/modules/Admin/Language/en/PodcastImport.php b/modules/Admin/Language/en/PodcastImport.php deleted file mode 100644 index 7c3ef67d..00000000 --- a/modules/Admin/Language/en/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'The podcast to import', - 'old_podcast_section_subtitle' => - 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', - 'imported_feed_url' => 'Feed URL', - 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', - 'new_podcast_section_title' => 'The new podcast', - 'advanced_params_section_title' => 'Advanced parameters', - 'advanced_params_section_subtitle' => - 'Keep the default values if you have no idea of what the fields are for.', - 'slug_field' => 'Field to be used to calculate episode slug', - 'description_field' => - 'Source field used for episode description / show notes', - 'force_renumber' => 'Force episodes renumbering', - 'force_renumber_hint' => - 'Use this if your podcast does not have episode numbers but wish to set them during import.', - 'season_number' => 'Season number', - 'season_number_hint' => - 'Use this if your podcast does not have a season number but wish to set one during import. Leave blank otherwise.', - 'max_episodes' => 'Maximum number of episodes to import', - 'max_episodes_hint' => 'Leave blank to import all episodes', - 'lock_import' => - 'This feed is protected. You cannot import it. If you are the owner, unprotect it on the origin platform.', - 'submit' => 'Import podcast', -]; diff --git a/modules/Admin/Language/en/PodcastNavigation.php b/modules/Admin/Language/en/PodcastNavigation.php index b4d7ddc0..a6b73e4a 100644 --- a/modules/Admin/Language/en/PodcastNavigation.php +++ b/modules/Admin/Language/en/PodcastNavigation.php @@ -10,13 +10,17 @@ declare(strict_types=1); return [ 'go_to_page' => 'Go to podcast page', + 'rss_feed' => 'RSS feed', 'dashboard' => 'Podcast dashboard', 'podcast-view' => 'Home', 'podcast-edit' => 'Edit podcast', 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', 'episodes' => 'Episodes', 'episode-list' => 'All episodes', 'episode-create' => 'New episode', + 'plugins' => 'Plugins', 'analytics' => 'Analytics', 'podcast-analytics' => 'Audience overview', 'podcast-analytics-webpages' => 'Web pages visits', @@ -25,14 +29,15 @@ return [ 'podcast-analytics-players' => 'Players', 'podcast-analytics-listening-time' => 'Listening time', 'podcast-analytics-time-periods' => 'Time periods', - 'premium' => 'Premium', + 'monetization' => 'Monetization', 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', + 'subscription-create' => 'Add subscription', 'contributors' => 'Contributors', 'contributor-list' => 'All contributors', 'contributor-add' => 'Add contributor', - 'platforms' => 'External platforms', - 'platforms-podcasting' => 'Podcasting', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', 'platforms-social' => 'Social networks', - 'platforms-funding' => 'Funding', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', ]; diff --git a/modules/Admin/Language/en/Validation.php b/modules/Admin/Language/en/Validation.php index 750b1968..4db0fe89 100644 --- a/modules/Admin/Language/en/Validation.php +++ b/modules/Admin/Language/en/Validation.php @@ -13,6 +13,7 @@ return [ '{field} is either not an image, or it is not wide or tall enough.', 'is_image_ratio' => '{field} is either not an image or not of the right ratio.', - 'validate_url' => - 'The {field} field must be a valid URL (eg. https://example.com/).', + 'is_json' => '{field} contains invalid JSON.', + 'is_boolean' => 'The {field} field must be a boolean (true or false).', + 'is_list' => 'The {field} field must be an array.', ]; diff --git a/modules/Admin/Language/es/AboutCastopod.php b/modules/Admin/Language/es/AboutCastopod.php new file mode 100644 index 00000000..93d4142b --- /dev/null +++ b/modules/Admin/Language/es/AboutCastopod.php @@ -0,0 +1,22 @@ + 'Acerca de Castopod', + 'host_name' => 'Nombre del host', + 'version' => 'Versión de Castopod', + 'php_version' => 'Versión de PHP', + 'os' => 'Sistema Operativo', + 'languages' => 'Idiomas', + 'update_database' => 'Actualizar base de datos', + 'messages' => [ + 'databaseUpdateSuccess' => 'La base de datos esta actualizada!', + ], +]; diff --git a/modules/Admin/Language/es/Breadcrumb.php b/modules/Admin/Language/es/Breadcrumb.php index 621ca94e..6ffb6387 100644 --- a/modules/Admin/Language/es/Breadcrumb.php +++ b/modules/Admin/Language/es/Breadcrumb.php @@ -14,29 +14,34 @@ return [ ->gateway => 'Inicio', 'podcasts' => 'podcasts', 'episodes' => 'episodios', - 'subscriptions' => 'subscriptions', + 'subscriptions' => 'suscripciones', 'contributors' => 'colaboradores', 'pages' => 'páginas', 'settings' => 'configuración', 'theme' => 'tema', + 'about' => 'acerca de', 'add' => 'añadir', 'new' => 'nuevo', 'edit' => 'editar', 'persons' => 'personas', 'publish' => 'publicar', 'publish-edit' => 'editar publicación', - 'publish-date-edit' => 'edit publication date', + 'publish-date-edit' => 'editar fecha de publicación', 'unpublish' => 'anular publicación', 'delete' => 'borrar', + 'remove' => 'eliminar', 'fediverse' => 'fediverso', - 'block-lists' => 'listas de bloqueo', + 'blocked-actors' => 'actores bloqueado', + 'blocked-domains' => 'dominios bloqueados', 'users' => 'usuarios', 'my-account' => 'mi cuenta', 'change-password' => 'cambiar contraseña', - 'import' => 'importar feed', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', 'platforms' => 'plataformas', 'social' => 'redes sociales', 'funding' => 'financiación | fondos', + 'monetization-other' => 'other monetization', 'analytics' => 'estadísticas', 'locations' => 'ubicaciones', 'webpages' => 'páginas web', @@ -48,5 +53,5 @@ return [ 'video-clips' => 'clips de vídeo', 'embed' => 'reproductor embebido', 'notifications' => 'notificaciones', - 'suspend' => 'suspend', + 'suspend' => 'suspender', ]; diff --git a/modules/Admin/Language/es/Charts.php b/modules/Admin/Language/es/Charts.php index fee7dda4..7294231c 100644 --- a/modules/Admin/Language/es/Charts.php +++ b/modules/Admin/Language/es/Charts.php @@ -37,4 +37,5 @@ return [ 'podcast_by_bandwidth' => 'Ancho de banda diario usado (en MB)', 'total_storage_by_month' => 'Almacenamiento mensual (en MB)', 'total_bandwidth_by_month' => 'Ancho de banda mensual usado (en MB)', + 'total_bandwidth_by_month_limit' => 'Limitado a {totalBandwidth} al mes', ]; diff --git a/modules/Admin/Language/es/Common.php b/modules/Admin/Language/es/Common.php index b64ee73d..ff3987cc 100644 --- a/modules/Admin/Language/es/Common.php +++ b/modules/Admin/Language/es/Common.php @@ -40,6 +40,7 @@ return [ ], 'upload_file' => 'Subir un archivo', 'remote_url' => 'URL remoto', + 'save' => 'Save', ], 'play_episode_button' => [ 'play' => 'Reproducir', diff --git a/modules/Admin/Language/es/Episode.php b/modules/Admin/Language/es/Episode.php index 4cb492e8..48f93ad4 100644 --- a/modules/Admin/Language/es/Episode.php +++ b/modules/Admin/Language/es/Episode.php @@ -22,16 +22,17 @@ return [ 'all_podcast_episodes' => 'Todos los episodios del podcast', 'back_to_podcast' => 'Regresar al podcast', 'edit' => 'Editar', + 'preview' => 'Preview', 'publish' => 'Publicar', 'publish_edit' => 'Editar publicación', - 'publish_date_edit' => 'Edit publication date', + 'publish_date_edit' => 'Editar fecha de publicación', 'unpublish' => 'Anular publicación', 'publish_error' => 'El episodio ya está publicado.', 'publish_edit_error' => 'El episodio ya está publicado.', 'publish_cancel_error' => 'El episodio ya está publicado.', - 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', - 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', - 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'publish_date_edit_error' => 'El episodio no ha sido publicado todavía, no puede editar su fecha de publicación.', + 'publish_date_edit_future_error' => '¡La fecha de publicación del episodio sólo puede establecerse a una fecha pasada! Si desea reprogramarla, despublicarla primero.', + 'publish_date_edit_success' => '¡La fecha de publicación del episodio se ha actualizado con éxito!', 'unpublish_error' => 'El episodio no está publicado.', 'delete' => 'Borrar', 'go_to_page' => 'Ir a la página', @@ -55,6 +56,7 @@ return [ }', 'episode' => 'Episodio', 'visibility' => 'Visibilidad', + 'downloads' => 'Descargas', 'comments' => 'Comentarios', 'actions' => 'Acciones', ], @@ -79,13 +81,13 @@ return [ audio {audio} other {media} }.', - 'deleteFileError' => 'Hubo un problema al tratar de eliminar el archivo {file_path} {type, select, - transcript {de la transcripción} - chapters {de los episodios} - image {de la portada} - audio {del audio} - other {} - }. Puedes eliminarlo manualmente de tu disco.', + 'deleteFileError' => 'Failed to delete {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'Ya existe un episodio con el slug elegido.', ], 'form' => [ @@ -114,7 +116,7 @@ return [ 'bonus_hint' => 'Contenido extra para la serie (por ejemplo, detrás de escenas o entrevistas con el elenco) o contenido promocional para otra serie', ], 'premium_title' => 'Premium', - 'premium' => 'Episode must be accessible to premium subscribers only', + 'premium' => 'El episodio debe ser accesible solamente para los suscriptores premium', 'parental_advisory' => [ 'label' => 'Aviso parental', 'hint' => '¿El episodio contiene contenido explícito?', @@ -137,9 +139,9 @@ return [ 'location_name' => 'Nombre o dirección de ubicación', 'location_name_hint' => 'Esta puede ser una ubicación real o ficticia', 'transcript' => 'Transcripción (subtítulos / subtítulos ocultos)', - 'transcript_hint' => 'Sólo se permiten .srt.', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', 'transcript_download' => 'Descargar transcripción', - 'transcript_file' => 'Archivo de transcripción (.srt)', + 'transcript_file' => 'Transcript file (.srt or .vtt)', 'transcript_remote_url' => 'Url remota para transcripción', 'transcript_file_delete' => 'Eliminar archivo de transcripción', 'chapters' => 'Capítulos', @@ -183,9 +185,9 @@ return [ 'message_warning_submit' => 'Publicar de todos modos', ], 'publish_date_edit_form' => [ - 'new_publication_date' => 'New publication date', - 'new_publication_date_hint' => 'Must be set to a past date.', - 'submit' => 'Edit publication date', + 'new_publication_date' => 'Nueva fecha de publicación', + 'new_publication_date_hint' => 'Debe establecerse en una fecha pasada.', + 'submit' => 'Editar fecha de publicación', ], 'unpublish_form' => [ 'disclaimer' => @@ -210,4 +212,14 @@ return [ 'light' => 'Claro', 'light-transparent' => 'Transparente claro', ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], ]; diff --git a/modules/Admin/Language/es/Navigation.php b/modules/Admin/Language/es/Navigation.php index 43c58893..c8936b27 100644 --- a/modules/Admin/Language/es/Navigation.php +++ b/modules/Admin/Language/es/Navigation.php @@ -12,12 +12,14 @@ return [ 'toggle_sidebar' => 'Ocultar/mostrar barra lateral', 'go_to_website' => 'Ir al sitio web', 'go_to_admin' => 'Ir al panel de administración', + 'not-authorized' => 'Not authorized', 'dashboard' => 'Panel de control', 'admin' => 'Inicio', 'podcasts' => 'Podcasts', 'podcast-list' => 'Todos los podcasts', 'podcast-create' => 'Nuevo podcast', - 'podcast-import' => 'Importar un podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', 'persons' => 'Personas', 'person-list' => 'Todas las personas', 'person-create' => 'Nueva persona', @@ -33,6 +35,7 @@ return [ 'settings' => 'Configuración', 'settings-general' => 'General', 'settings-theme' => 'Tema', + 'admin-about' => 'About', 'account' => [ 'my-account' => 'Mi cuenta', 'change-password' => 'Cambiar contraseña', diff --git a/modules/Admin/Language/es/Notifications.php b/modules/Admin/Language/es/Notifications.php index d6c93ba9..70e375fb 100644 --- a/modules/Admin/Language/es/Notifications.php +++ b/modules/Admin/Language/es/Notifications.php @@ -13,7 +13,7 @@ return [ 'reply' => '{actor_username} respondió a tu publicación', 'favourite' => '{actor_username} ha marcado como favorita tu publicación', 'reblog' => '{actor_username} ha compartido tu publicación', - 'follow' => '{actor_username} started following you', + 'follow' => '{actor_username} ha empezado a seguirte', 'no_notifications' => 'No hay notificaciones', 'mark_all_as_read' => 'Marcar todas como leídas', ]; diff --git a/modules/Admin/Language/es/Platforms.php b/modules/Admin/Language/es/Platforms.php index 48effd90..f045e36c 100644 --- a/modules/Admin/Language/es/Platforms.php +++ b/modules/Admin/Language/es/Platforms.php @@ -9,9 +9,22 @@ declare(strict_types=1); */ return [ - 'title' => 'Plataformas', + 'title' => [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', 'home_url' => 'Ir al sitio web de {platformName}', + 'register' => 'Register', 'submit_url' => 'Envía tu podcast en {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', 'visible' => '¿Mostrar en la página de inicio del podcast?', 'on_embed' => '¿Mostrar en el reproductor incrustable?', 'remove' => 'Eliminar {platformName}', diff --git a/modules/Admin/Language/es/Podcast.php b/modules/Admin/Language/es/Podcast.php index c10b2374..3786cfda 100644 --- a/modules/Admin/Language/es/Podcast.php +++ b/modules/Admin/Language/es/Podcast.php @@ -13,6 +13,7 @@ return [ 'no_podcast' => 'No se encontró el podcast!', 'create' => 'Crear podcasts', 'import' => 'Importar un podcast', + 'all_imports' => 'Podcast imports', 'new_episode' => 'Nuevo episodio', 'view' => 'Ver Podcast', 'edit' => 'Editar el Podcast', @@ -21,6 +22,7 @@ return [ 'delete' => 'Eliminar podcast', 'see_episodes' => 'Ver episodios', 'see_contributors' => 'Ver colaboradores', + 'monetization_other' => 'Other monetization', 'go_to_page' => 'Ir a la página', 'latest_episodes' => 'Últimos Episodios', 'see_all_episodes' => 'Mostrar todos los episodios', @@ -48,7 +50,6 @@ return [ other {se han añadido # episodios} } al podcast!', 'podcastFeedUpToDate' => 'El Podcast ya está actualizado.', - 'podcastNotImported' => 'Podcast no pudo ser actualizado porque no fue importado.', 'publishError' => 'Este podcast ya ha sido publicado o está programado para su publicación.', 'publishEditError' => 'Este podcast no está programado para ser publicado.', 'publishCancelSuccess' => '¡La publicación del podcast ha sido cancelada con éxito!', @@ -57,6 +58,8 @@ return [ 'form' => [ 'identity_section_title' => 'Identidad de Podcast', 'identity_section_subtitle' => 'Estos campos le permiten recibir un aviso.', + 'fediverse_section_title' => 'Fediverse identity', + 'cover' => 'Portada de Podcast', 'cover_size_hint' => 'La portada debe ser cuadrada y con al menos 1400 px de ancho y alto.', 'banner' => 'Cartel del Podcast', @@ -71,7 +74,17 @@ return [ 'episodic' => 'Por episodios', 'episodic_hint' => 'Si los episodios están destinados a ser consumidos sin ningún orden específico. Los episodios más nuevos serán presentados primero.', 'serial' => 'Serial', - 'serial_hint' => 'Si los episodios están destinados a ser consumidos en orden secuencial. Los episodios más antiguos serán presentados primero.', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', ], 'description' => 'Descripción', 'classification_section_title' => 'Clasificación', @@ -96,6 +109,8 @@ return [ 'owner_email' => 'Email del propietario', 'owner_email_hint' => 'Será utilizado por la mayoría de las plataformas para verificar la propiedad del podcast. Visible en el feed RSS público.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', 'publisher' => 'Publicador', 'publisher_hint' => 'El grupo responsable de la creación del espectáculo. A menudo se refiere a la empresa matriz o red de un podcast. Este campo a veces se etiqueta como \'Autor\'.', @@ -108,8 +123,13 @@ return [ 'monetization_section_subtitle' => 'Gana dinero gracias a tu audiencia.', 'premium' => 'Premium', - 'premium_by_default' => 'Episodes must be set as premium by default', - 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'premium_by_default' => 'Los episodios deben establecerse como premium por defecto', + 'premium_by_default_hint' => 'Los episodios Podcast se marcarán como premium de forma predeterminada. Todavía puedes elegir establecer algunos episodios, trailers o bonificaciones como públicos.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Analiza tus datos de estadísticas con OP3, un servicio de analíticas fiable y de código abierto. Comparte, valida y compara tus estadísticas con el ecosistema de podcasting abierto.', + 'op3_enable' => 'Activa el servicio de estadísticas OP3', + 'op3_enable_hint' => 'Por motivos de seguridad, las estadísticas de los episodios premium no serán compartidas con OP3.', 'payment_pointer' => 'Puntero de pago para Monetización web', 'payment_pointer_hint' => 'Aquí es donde usted recibirá dinero gracias a la Monetización Web', @@ -118,11 +138,12 @@ return [ 'Si necesita etiquetas RSS que Castopod no maneja, póngalas aquí.', 'custom_rss' => 'Etiquetas RSS personalizadas para el podcast', 'custom_rss_hint' => 'Esto se inyectará dentro de la etiqueta de canal.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', 'new_feed_url' => 'Nueva URL de feed', 'new_feed_url_hint' => 'Utilice este campo cuando se mueva a otro dominio o plataforma de alojamiento podcast. De forma predeterminada, el valor se establece en la URL actual de RSS si el podcast es importado.', 'old_feed_url' => 'Antigua URL del feed RSS', - 'update_feed' => 'Actualizar el feed', - 'update_feed_tip' => 'Importar los últimos episodios de este podcast', 'partnership' => 'Asociación', 'partner_id' => 'ID', 'partner_link_url' => 'URL del enlace', @@ -130,7 +151,6 @@ return [ 'partner_id_hint' => 'Su propio ID de socio', 'partner_link_url_hint' => 'La dirección genérica de enlace de socio', 'partner_image_url_hint' => 'La dirección de imagen genérica del socio', - 'status_section_title' => 'Estado', 'block' => 'El podcast debe ocultarse a los catálogos públicos', 'block_hint' => 'El estado de visibilidad del podcast: al activar esto se impide que este podcast al completo aparezca en Apple Podcasts, Google Podcasts y cualquier aplicación de terceros que extraiga episodios de estos directorios. (no está garantizado al 100%)', diff --git a/modules/Admin/Language/es/PodcastImport.php b/modules/Admin/Language/es/PodcastImport.php deleted file mode 100644 index 96cef94d..00000000 --- a/modules/Admin/Language/es/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'Este procedimiento puede llevar mucho tiempo. Como la versión actual no muestra ningún progreso mientras se ejecuta, no verás nada actualizado hasta que termine. En el caso de recibir un mensaje de error por falta de tiempo (Timeout error), incrementa el valor `max_execution_time` en la configuración del PHP del servidor.', - 'old_podcast_section_title' => 'Podcasts para importar', - 'old_podcast_section_subtitle' => - 'Asegúrese de que tiene los derechos para este podcast antes de importarlo. Copiar y difundir un podcast sin los derechos apropiados es piratería y puede ser procesado.', - 'imported_feed_url' => 'URL del Feed', - 'imported_feed_url_hint' => 'El feed debe estar en formato xml o rss.', - 'new_podcast_section_title' => 'El nuevo Podcast', - 'advanced_params_section_title' => 'Parámetros avanzados', - 'advanced_params_section_subtitle' => - 'Mantenga los valores por defecto si no tiene idea de para qué sirven los campos.', - 'slug_field' => 'Campo a utilizar para calcular el slug de episodio', - 'description_field' => - 'Campo de origen usado para la descripción del episodio / mostrar notas', - 'force_renumber' => 'Forzar renumeración de episodios', - 'force_renumber_hint' => - 'Utilice esto si su podcast no tiene números de episodios pero desea establecerlos durante la importación.', - 'season_number' => 'Número de Temporada', - 'season_number_hint' => - 'Utilice esto si su podcast no tiene un número de temporada pero desea establecer uno durante la importación. Deje en blanco de lo contrario.', - 'max_episodes' => 'Número máximo de episodios a importar', - 'max_episodes_hint' => 'Dejar en blanco para importar todos los episodios', - 'lock_import' => - 'Este feed está protegido. No puedes importarlo. Si eres el propietario, debes desprotegerlo en la plataforma de origen.', - 'submit' => 'Importar podcast', -]; diff --git a/modules/Admin/Language/es/PodcastNavigation.php b/modules/Admin/Language/es/PodcastNavigation.php index 8bbe1733..43a142bb 100644 --- a/modules/Admin/Language/es/PodcastNavigation.php +++ b/modules/Admin/Language/es/PodcastNavigation.php @@ -10,10 +10,13 @@ declare(strict_types=1); return [ 'go_to_page' => 'Ir a la página del podcast', + 'rss_feed' => 'RSS feed', 'dashboard' => 'Panel de Podcast', 'podcast-view' => 'Inicio', 'podcast-edit' => 'Editar podcast', 'podcast-persons-manage' => 'Administrar personas', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', 'episodes' => 'Episodios', 'episode-list' => 'Todos los episodios', 'episode-create' => 'Nuevo episodio', @@ -25,14 +28,15 @@ return [ 'podcast-analytics-players' => 'Reproductores', 'podcast-analytics-listening-time' => 'Tiempo de escucha', 'podcast-analytics-time-periods' => 'Periodos de tiempo', - 'premium' => 'Premium', - 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', + 'monetization' => 'Monetization', + 'subscription-list' => 'Todas las suscripciones', + 'subscription-create' => 'Add subscription', 'contributors' => 'Colaboradores', 'contributor-list' => 'Todos los colaboradores', 'contributor-add' => 'Añadir colaborador', - 'platforms' => 'Plataformas externas', - 'platforms-podcasting' => 'Podcasting', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', 'platforms-social' => 'Redes sociales', - 'platforms-funding' => 'Financiamiento', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', ]; diff --git a/modules/Admin/Language/es/Settings.php b/modules/Admin/Language/es/Settings.php index 245ee615..6c63c82d 100644 --- a/modules/Admin/Language/es/Settings.php +++ b/modules/Admin/Language/es/Settings.php @@ -35,8 +35,8 @@ return [ 'reset_counts_helper' => 'Esta opción recalculará y restablecerá todos los conteos de datos (número de seguidores, publicaciones, comentarios, …).', 'rewrite_media' => 'Reescribir metadatos de medios', 'rewrite_media_helper' => 'Esta opción eliminará todos los archivos multimedia superfluos y los volverá a crear (imágenes, archivos de audio, transcripciones, capítulos, …)', - 'rename_episodes_files' => 'Rename episode audio files', - 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', + 'rename_episodes_files' => 'Renombrar archivos de audio del episodio', + 'rename_episodes_files_hint' => 'Esta opción renombrará todos los archivos de audio de episodios a una cadena aleatoria de caracteres. Usa esto si uno de tus episodios privados fue filtrado ya que esto lo ocultará efectivamente.', 'clear_cache' => 'Borrar toda la caché', 'clear_cache_helper' => 'Esta opción eliminará la caché de redis o archivos de escritura/caché.', 'run' => 'Ejecutar tareas de mantenimiento', diff --git a/modules/Admin/Language/es/Validation.php b/modules/Admin/Language/es/Validation.php index 7b2d4379..5478dfe8 100644 --- a/modules/Admin/Language/es/Validation.php +++ b/modules/Admin/Language/es/Validation.php @@ -13,6 +13,5 @@ return [ '{field} no es una imagen, o no es suficientemente ancha o alta.', 'is_image_ratio' => '{field} no es una imagen o no es de la proporción correcta.', - 'validate_url' => - 'El campo {field} debe ser una URL válida (ej. https://ejemplo.com/).', + 'is_json' => '{field} contains invalid JSON.', ]; diff --git a/modules/Admin/Language/eu/AboutCastopod.php b/modules/Admin/Language/eu/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/eu/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/eu/Breadcrumb.php b/modules/Admin/Language/eu/Breadcrumb.php new file mode 100644 index 00000000..408c9f9f --- /dev/null +++ b/modules/Admin/Language/eu/Breadcrumb.php @@ -0,0 +1,57 @@ + 'breadcrumb', + config('Admin') + ->gateway => 'Home', + 'podcasts' => 'podcasts', + 'episodes' => 'episodes', + 'subscriptions' => 'subscriptions', + 'contributors' => 'contributors', + 'pages' => 'pages', + 'settings' => 'settings', + 'theme' => 'theme', + 'about' => 'about', + 'add' => 'add', + 'new' => 'new', + 'edit' => 'edit', + 'persons' => 'persons', + 'publish' => 'publish', + 'publish-edit' => 'edit publication', + 'publish-date-edit' => 'edit publication date', + 'unpublish' => 'unpublish', + 'delete' => 'delete', + 'remove' => 'remove', + 'fediverse' => 'fediverse', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', + 'users' => 'users', + 'my-account' => 'my account', + 'change-password' => 'change password', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', + 'platforms' => 'platforms', + 'social' => 'social networks', + 'funding' => 'funding', + 'monetization-other' => 'other monetization', + 'analytics' => 'analytics', + 'locations' => 'locations', + 'webpages' => 'web pages', + 'unique-listeners' => 'unique listeners', + 'players' => 'players', + 'listening-time' => 'listening time', + 'time-periods' => 'time periods', + 'soundbites' => 'soundbites', + 'video-clips' => 'video clips', + 'embed' => 'embeddable player', + 'notifications' => 'notifications', + 'suspend' => 'suspend', +]; diff --git a/modules/Admin/Language/eu/Charts.php b/modules/Admin/Language/eu/Charts.php new file mode 100644 index 00000000..6ede2510 --- /dev/null +++ b/modules/Admin/Language/eu/Charts.php @@ -0,0 +1,41 @@ + 'Episode downloads by service (for the past week)', + 'by_player_weekly' => 'Episode downloads by player (for the past week)', + 'by_player_yearly' => 'Episode downloads by player (for the past year)', + 'by_device_weekly' => 'Episode downloads by device (for the past week)', + 'by_os_weekly' => 'Episode downloads by O.S. (for the past week)', + 'podcast_by_region' => 'Episode downloads by region (for the past week)', + 'unique_daily_listeners' => 'Daily unique listeners', + 'unique_monthly_listeners' => 'Monthly unique listeners', + 'by_browser' => 'Web pages usage by browser (for the past week)', + 'podcast_by_day' => 'Episode daily downloads', + 'podcast_by_month' => 'Episode monthly downloads', + 'episode_by_day' => 'Episode daily downloads (first 60 days)', + 'episode_by_month' => 'Episode monthly downloads', + 'episodes_by_day' => + '5 latest episodes downloads (during their first 60 days)', + 'by_country_weekly' => 'Episode downloads by country (for the past week)', + 'by_country_yearly' => 'Episode downloads by country (for the past year)', + 'by_domain_weekly' => 'Web pages visits by source (for the past week)', + 'by_domain_yearly' => 'Web pages visits by source (for the past year)', + 'by_entry_page' => 'Web pages visits by landing page (for the past week)', + 'podcast_bots' => 'Bots (crawlers)', + 'daily_listening_time' => 'Daily cumulative listening time', + 'monthly_listening_time' => 'Monthly cumulative listening time', + 'by_weekday' => 'By week day (for the past 60 days)', + 'by_hour' => 'By time of day (for the past 60 days)', + 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', + 'total_storage_by_month' => 'Monthly storage (in MB)', + 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', +]; diff --git a/modules/Admin/Language/eu/Common.php b/modules/Admin/Language/eu/Common.php new file mode 100644 index 00000000..74addcf2 --- /dev/null +++ b/modules/Admin/Language/eu/Common.php @@ -0,0 +1,52 @@ + 'Yes', + 'no' => 'No', + 'cancel' => 'Cancel', + 'optional' => 'Optional', + 'more' => 'More', + 'no_data' => 'No data found!', + 'close' => 'Close', + 'edit' => 'Edit', + 'copy' => 'Copy', + 'copied' => 'Copied!', + 'home' => 'Home', + 'explicit' => 'Explicit', + 'powered_by' => 'Powered by {castopod}', + 'actions' => 'Actions', + 'pageInfo' => 'Page {currentPage} out of {pageCount}', + 'go_back' => 'Go back', + 'forms' => [ + 'editor' => [ + 'write' => 'Write', + 'preview' => 'Preview', + 'help' => 'Powered by markdown', + ], + 'multiSelect' => [ + 'selectText' => 'Press to select', + 'loadingText' => 'Loading…', + 'noResultsText' => 'No results found', + 'noChoicesText' => 'No choices to choose from', + 'maxItemText' => 'Cannot add more items', + ], + 'upload_file' => 'Upload a file', + 'remote_url' => 'Remote URL', + 'save' => 'Save', + ], + 'play_episode_button' => [ + 'play' => 'Play', + 'playing' => 'Playing', + ], + 'size_limit' => 'Size limit: {0}.', + 'choose_interact' => 'Choose how to interact', + 'view' => 'View', +]; diff --git a/modules/Admin/Language/eu/Countries.php b/modules/Admin/Language/eu/Countries.php new file mode 100644 index 00000000..4cd5d9c8 --- /dev/null +++ b/modules/Admin/Language/eu/Countries.php @@ -0,0 +1,264 @@ + 'Andorra', + 'AE' => 'United Arab Emirates', + 'AF' => 'Afghanistan', + 'AG' => 'Antigua and Barbuda', + 'AI' => 'Anguilla', + 'AL' => 'Albania', + 'AM' => 'Armenia', + 'AO' => 'Angola', + 'AQ' => 'Antarctica', + 'AR' => 'Argentina', + 'AS' => 'American Samoa', + 'AT' => 'Austria', + 'AU' => 'Australia', + 'AW' => 'Aruba', + 'AX' => 'Åland Islands', + 'AZ' => 'Azerbaijan', + 'BA' => 'Bosnia and Herzegovina', + 'BB' => 'Barbados', + 'BD' => 'Bangladesh', + 'BE' => 'Belgium', + 'BF' => 'Burkina Faso', + 'BG' => 'Bulgaria', + 'BH' => 'Bahrain', + 'BI' => 'Burundi', + 'BJ' => 'Benin', + 'BL' => 'Saint Barthélemy', + 'BM' => 'Bermuda', + 'BN' => 'Brunei Darussalam', + 'BO' => 'Bolivia, Plurinational State of', + 'BQ' => 'Bonaire, Sint Eustatius and Saba', + 'BR' => 'Brazil', + 'BS' => 'Bahamas', + 'BT' => 'Bhutan', + 'BV' => 'Bouvet Island', + 'BW' => 'Botswana', + 'BY' => 'Belarus', + 'BZ' => 'Belize', + 'CA' => 'Canada', + 'CC' => 'Cocos (Keeling) Islands', + 'CD' => 'Congo, the Democratic Republic of the', + 'CF' => 'Central African Republic', + 'CG' => 'Congo', + 'CH' => 'Switzerland', + 'CI' => "Côte d'Ivoire", + 'CK' => 'Cook Islands', + 'CL' => 'Chile', + 'CM' => 'Cameroon', + 'CN' => 'China', + 'CO' => 'Colombia', + 'CR' => 'Costa Rica', + 'CU' => 'Cuba', + 'CV' => 'Cape Verde', + 'CW' => 'Curaçao', + 'CX' => 'Christmas Island', + 'CY' => 'Cyprus', + 'CZ' => 'Czech Republic', + 'DE' => 'Germany', + 'DJ' => 'Djibouti', + 'DK' => 'Denmark', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'DZ' => 'Algeria', + 'EC' => 'Ecuador', + 'EE' => 'Estonia', + 'EG' => 'Egypt', + 'EH' => 'Western Sahara', + 'ER' => 'Eritrea', + 'ES' => 'Spain', + 'ET' => 'Ethiopia', + 'FI' => 'Finland', + 'FJ' => 'Fiji', + 'FK' => 'Falkland Islands (Malvinas)', + 'FM' => 'Micronesia, Federated States of', + 'FO' => 'Faroe Islands', + 'FR' => 'France', + 'GA' => 'Gabon', + 'GB' => 'United Kingdom', + 'GD' => 'Grenada', + 'GE' => 'Georgia', + 'GF' => 'French Guiana', + 'GG' => 'Guernsey', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GL' => 'Greenland', + 'GM' => 'Gambia', + 'GN' => 'Guinea', + 'GP' => 'Guadeloupe', + 'GQ' => 'Equatorial Guinea', + 'GR' => 'Greece', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'GT' => 'Guatemala', + 'GU' => 'Guam', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HK' => 'Hong Kong', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HR' => 'Croatia', + 'HT' => 'Haiti', + 'HU' => 'Hungary', + 'ID' => 'Indonesia', + 'IE' => 'Ireland', + 'IL' => 'Israel', + 'IM' => 'Isle of Man', + 'IN' => 'India', + 'IO' => 'British Indian Ocean Territory', + 'IQ' => 'Iraq', + 'IR' => 'Iran, Islamic Republic of', + 'IS' => 'Iceland', + 'IT' => 'Italy', + 'JE' => 'Jersey', + 'JM' => 'Jamaica', + 'JO' => 'Jordan', + 'JP' => 'Japan', + 'KE' => 'Kenya', + 'KG' => 'Kyrgyzstan', + 'KH' => 'Cambodia', + 'KI' => 'Kiribati', + 'KM' => 'Comoros', + 'KN' => 'Saint Kitts and Nevis', + 'KP' => "Korea, Democratic People's Republic of", + 'KR' => 'Korea, Republic of', + 'KW' => 'Kuwait', + 'KY' => 'Cayman Islands', + 'KZ' => 'Kazakhstan', + 'LA' => "Lao People's Democratic Republic", + 'LB' => 'Lebanon', + 'LC' => 'Saint Lucia', + 'LI' => 'Liechtenstein', + 'LK' => 'Sri Lanka', + 'LR' => 'Liberia', + 'LS' => 'Lesotho', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'LV' => 'Latvia', + 'LY' => 'Libya', + 'MA' => 'Morocco', + 'MC' => 'Monaco', + 'MD' => 'Moldova, Republic of', + 'ME' => 'Montenegro', + 'MF' => 'Saint Martin (French part)', + 'MG' => 'Madagascar', + 'MH' => 'Marshall Islands', + 'MK' => 'Macedonia, the Former Yugoslav Republic of', + 'ML' => 'Mali', + 'MM' => 'Myanmar', + 'MN' => 'Mongolia', + 'MO' => 'Macao', + 'MP' => 'Northern Mariana Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MS' => 'Montserrat', + 'MT' => 'Malta', + 'MU' => 'Mauritius', + 'MV' => 'Maldives', + 'MW' => 'Malawi', + 'MX' => 'Mexico', + 'MY' => 'Malaysia', + 'MZ' => 'Mozambique', + 'N/A' => 'Not Applicable (local IP…)', + 'NA' => 'Namibia', + 'NC' => 'New Caledonia', + 'NE' => 'Niger', + 'NF' => 'Norfolk Island', + 'NG' => 'Nigeria', + 'NI' => 'Nicaragua', + 'NL' => 'Netherlands', + 'NO' => 'Norway', + 'NP' => 'Nepal', + 'NR' => 'Nauru', + 'NU' => 'Niue', + 'NZ' => 'New Zealand', + 'OM' => 'Oman', + 'PA' => 'Panama', + 'PE' => 'Peru', + 'PF' => 'French Polynesia', + 'PG' => 'Papua New Guinea', + 'PH' => 'Philippines', + 'PK' => 'Pakistan', + 'PL' => 'Poland', + 'PM' => 'Saint Pierre and Miquelon', + 'PN' => 'Pitcairn', + 'PR' => 'Puerto Rico', + 'PS' => 'Palestine, State of', + 'PT' => 'Portugal', + 'PW' => 'Palau', + 'PY' => 'Paraguay', + 'QA' => 'Qatar', + 'RE' => 'Réunion', + 'RO' => 'Romania', + 'RS' => 'Serbia', + 'RU' => 'Russian Federation', + 'RW' => 'Rwanda', + 'SA' => 'Saudi Arabia', + 'SB' => 'Solomon Islands', + 'SC' => 'Seychelles', + 'SD' => 'Sudan', + 'SE' => 'Sweden', + 'SG' => 'Singapore', + 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', + 'SI' => 'Slovenia', + 'SJ' => 'Svalbard and Jan Mayen', + 'SK' => 'Slovakia', + 'SL' => 'Sierra Leone', + 'SM' => 'San Marino', + 'SN' => 'Senegal', + 'SO' => 'Somalia', + 'SR' => 'Suriname', + 'SS' => 'South Sudan', + 'ST' => 'Sao Tome and Principe', + 'SV' => 'El Salvador', + 'SX' => 'Sint Maarten (Dutch part)', + 'SY' => 'Syrian Arab Republic', + 'SZ' => 'Swaziland', + 'TC' => 'Turks and Caicos Islands', + 'TD' => 'Chad', + 'TF' => 'French Southern Territories', + 'TG' => 'Togo', + 'TH' => 'Thailand', + 'TJ' => 'Tajikistan', + 'TK' => 'Tokelau', + 'TL' => 'Timor-Leste', + 'TM' => 'Turkmenistan', + 'TN' => 'Tunisia', + 'TO' => 'Tonga', + 'TR' => 'Turkey', + 'TT' => 'Trinidad and Tobago', + 'TV' => 'Tuvalu', + 'TW' => 'Taiwan, Province of China', + 'TZ' => 'Tanzania, United Republic of', + 'UA' => 'Ukraine', + 'UG' => 'Uganda', + 'UM' => 'United States Minor Outlying Islands', + 'US' => 'United States', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VA' => 'Holy See (Vatican City State)', + 'VC' => 'Saint Vincent and the Grenadines', + 'VE' => 'Venezuela, Bolivarian Republic of', + 'VG' => 'Virgin Islands, British', + 'VI' => 'Virgin Islands, U.S.', + 'VN' => 'Viet Nam', + 'VU' => 'Vanuatu', + 'WF' => 'Wallis and Futuna', + 'WS' => 'Samoa', + 'YE' => 'Yemen', + 'YT' => 'Mayotte', + 'ZA' => 'South Africa', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', +]; diff --git a/modules/Admin/Language/eu/Dashboard.php b/modules/Admin/Language/eu/Dashboard.php new file mode 100644 index 00000000..881073fd --- /dev/null +++ b/modules/Admin/Language/eu/Dashboard.php @@ -0,0 +1,28 @@ + 'Admin dashboard', + 'welcome_message' => 'Welcome to the admin area!', + 'podcasts' => [ + 'title' => 'Podcasts', + 'not_found' => 'No published podcast', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'episodes' => [ + 'title' => 'Episodes', + 'not_found' => 'No published episode', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'storage' => [ + 'title' => 'Storage', + 'subtitle' => '{totalUploaded} out of {totalStorage}', + ], +]; diff --git a/modules/Admin/Language/eu/Episode.php b/modules/Admin/Language/eu/Episode.php new file mode 100644 index 00000000..4fa846e3 --- /dev/null +++ b/modules/Admin/Language/eu/Episode.php @@ -0,0 +1,225 @@ + 'Season {seasonNumber}', + 'season_abbr' => 'S{seasonNumber}', + 'number' => 'Episode {episodeNumber}', + 'number_abbr' => 'Ep. {episodeNumber}', + 'season_episode' => 'Season {seasonNumber} episode {episodeNumber}', + 'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}', + 'number_of_comments' => '{numberOfComments, plural, + one {# comment} + other {# comments} + }', + 'all_podcast_episodes' => 'All podcast episodes', + 'back_to_podcast' => 'Go back to podcast', + 'edit' => 'Edit', + 'preview' => 'Preview', + 'publish' => 'Publish', + 'publish_edit' => 'Edit publication', + 'publish_date_edit' => 'Edit publication date', + 'unpublish' => 'Unpublish', + 'publish_error' => 'Episode is already published.', + 'publish_edit_error' => 'Episode is already published.', + 'publish_cancel_error' => 'Episode is already published.', + 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', + 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', + 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'unpublish_error' => 'Episode is not published.', + 'delete' => 'Delete', + 'go_to_page' => 'Go to page', + 'create' => 'Add an episode', + 'publication_status' => [ + 'published' => 'Published', + 'with_podcast' => 'Published', + 'scheduled' => 'Scheduled', + 'not_published' => 'Not published', + ], + 'with_podcast_hint' => 'To be published at the same time as the podcast', + 'list' => [ + 'search' => [ + 'placeholder' => 'Search for an episode', + 'clear' => 'Clear search', + 'submit' => 'Search', + ], + 'number_of_episodes' => '{numberOfEpisodes, plural, + one {# episode} + other {# episodes} + }', + 'episode' => 'Episode', + 'visibility' => 'Visibility', + 'downloads' => 'Downloads', + 'comments' => 'Comments', + 'actions' => 'Actions', + ], + 'messages' => [ + 'createSuccess' => 'Episode has been successfully created!', + 'editSuccess' => 'Episode has been successfully updated!', + 'publishSuccess' => '{publication_status, select, + published {Episode successfully published!} + scheduled {Episode publication successfully scheduled!} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not published.} + }', + 'publishCancelSuccess' => 'Episode publication successfully cancelled!', + 'unpublishBeforeDeleteTip' => 'You must unpublish the episode before deleting it.', + 'scheduleDateError' => 'Schedule date must be set!', + 'deletePublishedEpisodeError' => 'Please unpublish the episode before deleting it.', + 'deleteSuccess' => 'Episode successfully deleted!', + 'deleteError' => 'Failed to delete episode {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deleteFileError' => 'Failed to delete {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + } file {file_key}. You may manually remove it from your disk.', + 'sameSlugError' => 'An episode with the chosen slug already exists.', + ], + 'form' => [ + 'file_size_error' => + 'Your file size is too big! Max size is {0}. Increase the `memory_limit`, `upload_max_filesize` and `post_max_size` values in your php configuration file then restart your web server to upload your file.', + 'audio_file' => 'Audio file', + 'audio_file_hint' => 'Choose an .mp3 or .m4a audio file.', + 'info_section_title' => 'Episode info', + 'cover' => 'Episode cover', + 'cover_hint' => + 'If you do not set a cover, the podcast cover will be used instead.', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'title' => 'Title', + 'title_hint' => + 'Should contain a clear and concise episode name. Do not specify the episode or season numbers here.', + 'permalink' => 'Permalink', + 'season_number' => 'Season', + 'episode_number' => 'Episode', + 'type' => [ + 'label' => 'Type', + 'full' => 'Full', + 'full_hint' => 'Complete content (the episode)', + 'trailer' => 'Trailer', + 'trailer_hint' => 'Short, promotional piece of content that represents a preview of the current show', + 'bonus' => 'Bonus', + 'bonus_hint' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show', + ], + 'premium_title' => 'Premium', + 'premium' => 'Episode must be accessible to premium subscribers only', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does the episode contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'show_notes_section_title' => 'Show notes', + 'show_notes_section_subtitle' => + 'Up to 4000 characters, be clear and concise. Show notes help potential listeners in finding the episode.', + 'description' => 'Description', + 'description_footer' => 'Description footer', + 'description_footer_hint' => + 'This text is added at the end of each episode description, it is a good place to input your social links for example.', + 'additional_files_section_title' => 'Additional files', + 'additional_files_section_subtitle' => + 'These files may be used by other platforms to provide better experience to your audience. See the {podcastNamespaceLink} for more information.', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this episode about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real or fictional location', + 'transcript' => 'Transcript (subtitles / closed captions)', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', + 'transcript_download' => 'Download transcript', + 'transcript_file' => 'Transcript file (.srt or .vtt)', + 'transcript_remote_url' => 'Remote url for transcript', + 'transcript_file_delete' => 'Delete transcript file', + 'chapters' => 'Chapters', + 'chapters_hint' => 'File must be in JSON Chapters format.', + 'chapters_download' => 'Download chapters', + 'chapters_file' => 'Chapters file', + 'chapters_remote_url' => 'Remote url for chapters file', + 'chapters_file_delete' => 'Delete chapters file', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the episode', + 'custom_rss_hint' => 'This will be injected within the ❬item❭ tag.', + 'block' => 'Episode should be hidden from public catalogues', + 'block_hint' => + 'The episode show or hide status: toggling this on prevents the episode from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'submit_create' => 'Create episode', + 'submit_edit' => 'Save episode', + ], + 'publish_form' => [ + 'back_to_episode_dashboard' => 'Back to episode dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your episode. The message will be broadcasted to all your followers in the fediverse and be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + 'with_podcast' => 'Publish alongside podcast', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_clear' => 'Clear publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the episode release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit' => 'Publish', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your episode.', + 'message_warning_submit' => 'Publish anyways', + ], + 'publish_date_edit_form' => [ + 'new_publication_date' => 'New publication date', + 'new_publication_date_hint' => 'Must be set to a past date.', + 'submit' => 'Edit publication date', + ], + 'unpublish_form' => [ + 'disclaimer' => + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", + 'understand' => 'I understand, I want to unpublish the episode', + 'submit' => 'Unpublish', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.", + 'understand' => 'I understand, I want to delete the episode', + 'submit' => 'Delete', + ], + 'embed' => [ + 'title' => 'Embeddable player', + 'label' => + 'Pick a theme color, copy the embeddable player to clipboard, then paste it on your website.', + 'clipboard_iframe' => 'Copy embeddable player to clipboard', + 'clipboard_url' => 'Copy address to clipboard', + 'dark' => 'Dark', + 'dark-transparent' => 'Dark transparent', + 'light' => 'Light', + 'light-transparent' => 'Light transparent', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], +]; diff --git a/modules/Admin/Language/eu/EpisodeNavigation.php b/modules/Admin/Language/eu/EpisodeNavigation.php new file mode 100644 index 00000000..1406e301 --- /dev/null +++ b/modules/Admin/Language/eu/EpisodeNavigation.php @@ -0,0 +1,23 @@ + 'View episode page', + 'dashboard' => 'Episode dashboard', + 'episode-view' => 'Home', + 'episode-edit' => 'Edit episode', + 'episode-persons-manage' => 'Manage persons', + 'embed-add' => 'Embeddable player', + 'clips' => 'Clips', + 'video-clips-list' => 'Video clips', + 'video-clips-create' => 'New video clip', + 'soundbites-list' => 'Soundbites', + 'soundbites-create' => 'New soundbite', +]; diff --git a/modules/Admin/Language/eu/Fediverse.php b/modules/Admin/Language/eu/Fediverse.php new file mode 100644 index 00000000..0e4ca66d --- /dev/null +++ b/modules/Admin/Language/eu/Fediverse.php @@ -0,0 +1,32 @@ + [ + 'actorNotFound' => 'The account could not be found!', + 'blockActorSuccess' => '{actor} has been blocked!', + 'unblockActorSuccess' => 'Actor has been unblocked!', + 'blockDomainSuccess' => '{domain} has been blocked!', + 'unblockDomainSuccess' => '{domain} has been unblocked!', + ], + 'blocked_actors' => 'Blocked accounts', + 'blocked_domains' => 'Blocked domains', + 'block_lists_form' => [ + 'handle' => 'Account handle', + 'handle_hint' => 'Input @username@domain account.', + 'domain' => 'Domain name', + 'submit' => 'Block!', + ], + 'list' => [ + 'actor' => 'Account', + 'domain' => 'Domain name', + 'unblock' => 'Unblock', + ], +]; diff --git a/modules/Admin/Language/eu/Home.php b/modules/Admin/Language/eu/Home.php new file mode 100644 index 00000000..3ff4c04d --- /dev/null +++ b/modules/Admin/Language/eu/Home.php @@ -0,0 +1,14 @@ + 'All podcasts', + 'no_podcast' => 'No podcast found', +]; diff --git a/modules/Admin/Language/eu/Install.php b/modules/Admin/Language/eu/Install.php new file mode 100644 index 00000000..36e373a2 --- /dev/null +++ b/modules/Admin/Language/eu/Install.php @@ -0,0 +1,61 @@ + 'Manual configuration', + 'manual_config_subtitle' => + 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'form' => [ + 'instance_config' => 'Instance configuration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', + 'db_hostname' => 'Database hostname', + 'db_name' => 'Database name', + 'db_username' => 'Database username', + 'db_password' => 'Database password', + 'db_prefix' => 'Database prefix', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Cache handler', + 'cacheHandlerOptions' => [ + 'file' => 'File', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Next', + 'submit' => 'Finish install', + 'create_superadmin' => 'Create your superadmin account', + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Your superadmin account has been created successfully. Login to start podcasting!', + 'databaseConnectError' => + 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'writeError' => + "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + ], +]; diff --git a/modules/Admin/Language/eu/Navigation.php b/modules/Admin/Language/eu/Navigation.php new file mode 100644 index 00000000..f3ffb129 --- /dev/null +++ b/modules/Admin/Language/eu/Navigation.php @@ -0,0 +1,44 @@ + 'Toggle sidebar', + 'go_to_website' => 'Go to website', + 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', + 'dashboard' => 'Dashboard', + 'admin' => 'Home', + 'podcasts' => 'Podcasts', + 'podcast-list' => 'All podcasts', + 'podcast-create' => 'New podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', + 'persons' => 'Persons', + 'person-list' => 'All persons', + 'person-create' => 'New person', + 'fediverse' => 'Fediverse', + 'fediverse-blocked-actors' => 'Blocked accounts', + 'fediverse-blocked-domains' => 'Blocked domains', + 'users' => 'Users', + 'user-list' => 'All users', + 'user-create' => 'New user', + 'pages' => 'Pages', + 'page-list' => 'All pages', + 'page-create' => 'New Page', + 'settings' => 'Settings', + 'settings-general' => 'General', + 'settings-theme' => 'Theme', + 'admin-about' => 'About', + 'account' => [ + 'my-account' => 'My account', + 'change-password' => 'Change password', + 'logout' => 'Logout', + ], +]; diff --git a/modules/Admin/Language/pt-BR/Notifications.php b/modules/Admin/Language/eu/Notifications.php similarity index 100% rename from modules/Admin/Language/pt-BR/Notifications.php rename to modules/Admin/Language/eu/Notifications.php diff --git a/modules/Admin/Language/eu/Page.php b/modules/Admin/Language/eu/Page.php new file mode 100644 index 00000000..b6f49de5 --- /dev/null +++ b/modules/Admin/Language/eu/Page.php @@ -0,0 +1,30 @@ + 'Back to home', + 'page' => 'Page', + 'all_pages' => 'All pages', + 'create' => 'New page', + 'go_to_page' => 'Go to page', + 'edit' => 'Edit page', + 'delete' => 'Delete page', + 'form' => [ + 'title' => 'Title', + 'permalink' => 'Permalink', + 'content' => 'Content', + 'submit_create' => 'Create page', + 'submit_edit' => 'Save', + ], + 'messages' => [ + 'createSuccess' => 'The page “{pageTitle}” was created successfully!', + 'editSuccess' => 'The page was successfully updated!', + ], +]; diff --git a/modules/Admin/Language/eu/Pager.php b/modules/Admin/Language/eu/Pager.php new file mode 100644 index 00000000..e25ee638 --- /dev/null +++ b/modules/Admin/Language/eu/Pager.php @@ -0,0 +1,21 @@ + 'Page navigation', + 'first' => 'First', + 'previous' => 'Previous', + 'next' => 'Next', + 'last' => 'Last', + 'older' => 'Older', + 'newer' => 'Newer', + 'invalidTemplate' => '{0} is not a valid Pager template.', + 'invalidPaginationGroup' => '{0} is not a valid Pagination group.', +]; diff --git a/modules/Admin/Language/eu/Person.php b/modules/Admin/Language/eu/Person.php new file mode 100644 index 00000000..a652be9f --- /dev/null +++ b/modules/Admin/Language/eu/Person.php @@ -0,0 +1,65 @@ + 'Persons', + 'all_persons' => 'All persons', + 'no_person' => 'Nobody found!', + 'create' => 'Create a person', + 'view' => 'View person', + 'edit' => 'Edit person', + 'delete' => 'Delete person', + 'messages' => [ + 'createSuccess' => 'Person has been successfully created!', + 'editSuccess' => 'Person has been successfully updated!', + 'deleteSuccess' => 'Person has been removed!', + ], + 'form' => [ + 'avatar' => 'Avatar', + 'avatar_size_hint' => + 'Avatar must be squared and at least 400px wide and tall.', + 'full_name' => 'Full name', + 'full_name_hint' => 'This is the full name or alias of the person.', + 'unique_name' => 'Unique name', + 'unique_name_hint' => 'Used for URLs', + 'information_url' => 'Information URL', + 'information_url_hint' => + 'Url to a relevant resource of information about the person, such as a homepage or third-party profile platform.', + 'submit_create' => 'Create person', + 'submit_edit' => 'Save person', + ], + 'podcast_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this podcast', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'episode_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this episode', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'credits' => 'Credits', +]; diff --git a/modules/Admin/Language/eu/Platforms.php b/modules/Admin/Language/eu/Platforms.php new file mode 100644 index 00000000..e161181c --- /dev/null +++ b/modules/Admin/Language/eu/Platforms.php @@ -0,0 +1,43 @@ + [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', + 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', + 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', + 'visible' => 'Display in podcast homepage?', + 'on_embed' => 'Display on embeddable player?', + 'remove' => 'Remove {platformName}', + 'submit' => 'Save', + 'messages' => [ + 'updateSuccess' => 'Platform links have been successfully updated!', + 'removeLinkSuccess' => 'The platform link has been removed.', + 'removeLinkError' => + 'The platform link could not be removed. Try again.', + ], + 'description' => [ + 'podcasting' => 'The podcast ID on this platform', + 'social' => 'The podcast account ID on this platform', + 'funding' => 'Call to action message', + ], +]; diff --git a/modules/Admin/Language/eu/Podcast.php b/modules/Admin/Language/eu/Podcast.php new file mode 100644 index 00000000..ff0daebc --- /dev/null +++ b/modules/Admin/Language/eu/Podcast.php @@ -0,0 +1,330 @@ + 'All podcasts', + 'no_podcast' => 'No podcast found!', + 'create' => 'Create podcast', + 'import' => 'Import podcast', + 'all_imports' => 'Podcast imports', + 'new_episode' => 'New Episode', + 'view' => 'View podcast', + 'edit' => 'Edit podcast', + 'publish' => 'Publish podcast', + 'publish_edit' => 'Edit publication', + 'delete' => 'Delete podcast', + 'see_episodes' => 'See episodes', + 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', + 'go_to_page' => 'Go to page', + 'latest_episodes' => 'Latest episodes', + 'see_all_episodes' => 'See all episodes', + 'draft' => 'Draft', + 'messages' => [ + 'createSuccess' => 'Podcast successfully created!', + 'editSuccess' => 'Podcast has been successfully updated!', + 'importSuccess' => 'Podcast has been successfully imported!', + 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', + 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, + cover {cover} + banner {banner} + other {media} + }.', + 'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.', + 'podcastFeedUpdateSuccess' => 'Successful update: {number_of_new_episodes, plural, + one {# episode was} + other {# episodes were} + } added to the podcast!', + 'podcastFeedUpToDate' => 'Podcast is already up to date.', + 'publishError' => 'This podcast is either already published or scheduled for publication.', + 'publishEditError' => 'This podcast is not scheduled for publication.', + 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', + 'scheduleDateError' => 'Schedule date must be set!', + ], + 'form' => [ + 'identity_section_title' => 'Podcast identity', + 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + + 'cover' => 'Podcast cover', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'banner' => 'Podcast banner', + 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', + 'banner_delete' => 'Delete podcast banner', + 'title' => 'Title', + 'handle' => 'Handle', + 'handle_hint' => + 'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.', + 'type' => [ + 'label' => 'Type', + 'episodic' => 'Episodic', + 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', + 'serial' => 'Serial', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + ], + 'description' => 'Description', + 'classification_section_title' => 'Classification', + 'classification_section_subtitle' => + 'These fields will impact your audience and competition.', + 'language' => 'Language', + 'category' => 'Category', + 'category_placeholder' => 'Select a category…', + 'other_categories' => 'Other categories', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does it contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'author_section_title' => 'Author', + 'author_section_subtitle' => 'Who is managing the podcast?', + 'owner_name' => 'Owner name', + 'owner_name_hint' => + 'For administrative use only. Visible in the public RSS feed.', + 'owner_email' => 'Owner email', + 'owner_email_hint' => + 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', + 'publisher' => 'Publisher', + 'publisher_hint' => + 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', + 'copyright' => 'Copyright', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this podcast about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real place or fictional', + 'monetization_section_title' => 'Monetization', + 'monetization_section_subtitle' => + 'Earn money thanks to your audience.', + 'premium' => 'Premium', + 'premium_by_default' => 'Episodes must be set as premium by default', + 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', + 'payment_pointer' => 'Payment Pointer for Web Monetization', + 'payment_pointer_hint' => + 'This is your where you will receive money thanks to Web Monetization', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the podcast', + 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', + 'new_feed_url' => 'New feed URL', + 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', + 'old_feed_url' => 'Old feed URL', + 'partnership' => 'Partnership', + 'partner_id' => 'ID', + 'partner_link_url' => 'Link URL', + 'partner_image_url' => 'Image URL', + 'partner_id_hint' => 'Your own partner ID', + 'partner_link_url_hint' => 'The generic partner link address', + 'partner_image_url_hint' => 'The generic partner image address', + 'block' => 'Podcast should be hidden from public catalogues', + 'block_hint' => + 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'complete' => 'Podcast will not be having new episodes', + 'lock' => 'Prevent podcast from being copied', + 'lock_hint' => + 'The purpose is to tell other podcast platforms whether they are allowed to import this feed. A value of yes means that any attempt to import this feed into a new platform should be rejected.', + 'submit_create' => 'Create podcast', + 'submit_edit' => 'Save podcast', + ], + 'category_options' => [ + 'uncategorized' => 'uncategorized', + 'arts' => 'Arts', + 'business' => 'Business', + 'comedy' => 'Comedy', + 'education' => 'Education', + 'fiction' => 'Fiction', + 'government' => 'Government', + 'health_and_fitness' => 'Health & Fitness', + 'history' => 'History', + 'kids_and_family' => 'Kids & Family', + 'leisure' => 'Leisure', + 'music' => 'Music', + 'news' => 'News', + 'religion_and_spirituality' => 'Religion & Spirituality', + 'science' => 'Science', + 'society_and_culture' => 'Society & Culture', + 'sports' => 'Sports', + 'technology' => 'Technology', + 'true_crime' => 'True Crime', + 'tv_and_film' => 'TV & Film', + 'books' => 'Books', + 'design' => 'Design', + 'fashion_and_beauty' => 'Fashion & Beauty', + 'food' => 'Food', + 'performing_arts' => 'Performing Arts', + 'visual_arts' => 'Visual Arts', + 'careers' => 'Careers', + 'entrepreneurship' => 'Entrepreneurship', + 'investing' => 'Investing', + 'management' => 'Management', + 'marketing' => 'Marketing', + 'non_profit' => 'Non-Profit', + 'comedy_interviews' => 'Comedy Interviews', + 'improv' => 'Improv', + 'stand_up' => 'Stand-Up', + 'courses' => 'Courses', + 'how_to' => 'How To', + 'language_learning' => 'Language Learning', + 'self_improvement' => 'Self-Improvement', + 'comedy_fiction' => 'Comedy Fiction', + 'drama' => 'Drama', + 'science_fiction' => 'Science Fiction', + 'alternative_health' => 'Alternative Health', + 'fitness' => 'Fitness', + 'medicine' => 'Medicine', + 'mental_health' => 'Mental Health', + 'nutrition' => 'Nutrition', + 'sexuality' => 'Sexuality', + 'education_for_kids' => 'Education for Kids', + 'parenting' => 'Parenting', + 'pets_and_animals' => 'Pets & Animals', + 'stories_for_kids' => 'Stories for Kids', + 'animation_and_manga' => 'Animation & Manga', + 'automotive' => 'Automotive', + 'aviation' => 'Aviation', + 'crafts' => 'Crafts', + 'games' => 'Games', + 'hobbies' => 'Hobbies', + 'home_and_garden' => 'Home & Garden', + 'video_games' => 'Video Games', + 'music_commentary' => 'Music Commentary', + 'music_history' => 'Music History', + 'music_interviews' => 'Music Interviews', + 'business_news' => 'Business News', + 'daily_news' => 'Daily News', + 'entertainment_news' => 'Entertainment News', + 'news_commentary' => 'News Commentary', + 'politics' => 'Politics', + 'sports_news' => 'Sports News', + 'tech_news' => 'Tech News', + 'buddhism' => 'Buddhism', + 'christianity' => 'Christianity', + 'hinduism' => 'Hinduism', + 'islam' => 'Islam', + 'judaism' => 'Judaism', + 'religion' => 'Religion', + 'spirituality' => 'Spirituality', + 'astronomy' => 'Astronomy', + 'chemistry' => 'Chemistry', + 'earth_sciences' => 'Earth Sciences', + 'life_sciences' => 'Life Sciences', + 'mathematics' => 'Mathematics', + 'natural_sciences' => 'Natural Sciences', + 'nature' => 'Nature', + 'physics' => 'Physics', + 'social_sciences' => 'Social Sciences', + 'documentary' => 'Documentary', + 'personal_journals' => 'Personal Journals', + 'philosophy' => 'Philosophy', + 'places_and_travel' => 'Places & Travel', + 'relationships' => 'Relationships', + 'baseball' => 'Baseball', + 'basketball' => 'Basketball', + 'cricket' => 'Cricket', + 'fantasy_sports' => 'Fantasy Sports', + 'football' => 'Football', + 'golf' => 'Golf', + 'hockey' => 'Hockey', + 'rugby' => 'Rugby', + 'running' => 'Running', + 'soccer' => 'Soccer', + 'swimming' => 'Swimming', + 'tennis' => 'Tennis', + 'volleyball' => 'Volleyball', + 'wilderness' => 'Wilderness', + 'wrestling' => 'Wrestling', + 'after_shows' => 'After Shows', + 'film_history' => 'Film History', + 'film_interviews' => 'Film Interviews', + 'film_reviews' => 'Film Reviews', + 'tv_reviews' => 'TV Reviews', + ], + 'publish_form' => [ + 'back_to_podcast_dashboard' => 'Back to podcast dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'submit' => 'Publish', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', + 'message_warning_submit' => 'Publish anyway', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'not_published' => 'This podcast is not yet published.', + 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", + 'understand' => 'I understand, I want the podcast to be permanently deleted', + 'submit' => 'Delete', + ], + 'by' => 'By {publisher}', + 'season' => 'Season {seasonNumber}', + 'list_of_episodes_year' => '{year} episodes ({episodeCount})', + 'list_of_episodes_season' => + 'Season {seasonNumber} episodes ({episodeCount})', + 'no_episode' => 'No episode found!', + 'follow' => 'Follow', + 'followers' => '{numberOfFollowers, plural, + one {# follower} + other {# followers} + }', + 'posts' => '{numberOfPosts, plural, + one {# post} + other {# posts} + }', + 'activity' => 'Activity', + 'episodes' => 'Episodes', + 'sponsor' => 'Sponsor', + 'funding_links' => 'Funding links for {podcastTitle}', + 'find_on' => 'Find {podcastTitle} on', + 'listen_on' => 'Listen on', +]; diff --git a/modules/Admin/Language/eu/PodcastNavigation.php b/modules/Admin/Language/eu/PodcastNavigation.php new file mode 100644 index 00000000..bb777707 --- /dev/null +++ b/modules/Admin/Language/eu/PodcastNavigation.php @@ -0,0 +1,42 @@ + 'Go to podcast page', + 'rss_feed' => 'RSS feed', + 'dashboard' => 'Podcast dashboard', + 'podcast-view' => 'Home', + 'podcast-edit' => 'Edit podcast', + 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', + 'episodes' => 'Episodes', + 'episode-list' => 'All episodes', + 'episode-create' => 'New episode', + 'analytics' => 'Analytics', + 'podcast-analytics' => 'Audience overview', + 'podcast-analytics-webpages' => 'Web pages visits', + 'podcast-analytics-locations' => 'Locations', + 'podcast-analytics-unique-listeners' => 'Unique listeners', + 'podcast-analytics-players' => 'Players', + 'podcast-analytics-listening-time' => 'Listening time', + 'podcast-analytics-time-periods' => 'Time periods', + 'monetization' => 'Monetization', + 'subscription-list' => 'All subscriptions', + 'subscription-create' => 'Add subscription', + 'contributors' => 'Contributors', + 'contributor-list' => 'All contributors', + 'contributor-add' => 'Add contributor', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', + 'platforms-social' => 'Social networks', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', +]; diff --git a/modules/Admin/Language/eu/Settings.php b/modules/Admin/Language/eu/Settings.php new file mode 100644 index 00000000..4a70dcba --- /dev/null +++ b/modules/Admin/Language/eu/Settings.php @@ -0,0 +1,58 @@ + 'General settings', + 'instance' => [ + 'title' => 'Instance', + 'site_icon' => 'Site icon', + 'site_icon_delete' => 'Delete site icon', + 'site_icon_hint' => 'Site icons are what you see on your browser tabs, bookmarks bar, and when you add a website as a shortcut on mobile devices.', + 'site_icon_helper' => 'Icon must be squared and at least 512px wide and tall.', + 'site_name' => 'Site name', + 'site_description' => 'Site description', + 'submit' => 'Save', + 'editSuccess' => 'Instance has been updated successfully!', + 'deleteIconSuccess' => 'Site icon has been remove successfully!', + ], + 'images' => [ + 'title' => 'Images', + 'subtitle' => 'Here you can regenerate all images based on the originals that were uploaded. To be used if you find that some images are missing. This task may take a while.', + 'regenerate' => 'Regenerate images', + 'regenerationSuccess' => 'All images have been regenerated successfully!', + ], + 'housekeeping' => [ + 'title' => 'Housekeeping', + 'subtitle' => 'Runs various housekeeping tasks. Use this feature if you ever encounter issues with media files or data integrity. These tasks may take a while.', + 'reset_counts' => 'Reset counts', + 'reset_counts_helper' => 'This option will recalculate and reset all data counts (number of followers, posts, comments, …).', + 'rewrite_media' => 'Rewrite media metadata', + 'rewrite_media_helper' => 'This option will delete all superfluous media files and recreate them (images, audio files, transcripts, chapters, …)', + 'rename_episodes_files' => 'Rename episode audio files', + 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', + 'clear_cache' => 'Clear all cache', + 'clear_cache_helper' => 'This option will flush redis cache or writable/cache files.', + 'run' => 'Run housekeeping', + 'runSuccess' => 'Housekeeping has been run successfully!', + ], + 'theme' => [ + 'title' => 'Theme', + 'accent_section_title' => 'Accent color', + 'accent_section_subtitle' => 'Choose the color to determine the look and feel of all public pages.', + 'pine' => 'Pine', + 'crimson' => 'Crimson', + 'amber' => 'Amber', + 'lake' => 'Lake', + 'jacaranda' => 'Jacaranda', + 'onyx' => 'Onyx', + 'submit' => 'Save', + 'setInstanceThemeSuccess' => 'Theme has been updated successfully!', + ], +]; diff --git a/modules/Admin/Language/eu/Soundbite.php b/modules/Admin/Language/eu/Soundbite.php new file mode 100644 index 00000000..a3f828fe --- /dev/null +++ b/modules/Admin/Language/eu/Soundbite.php @@ -0,0 +1,31 @@ + [ + 'title' => 'Soundbites', + 'soundbite' => 'Soundbite', + ], + 'messages' => [ + 'createSuccess' => 'Soundbite has been successfully created!', + 'deleteSuccess' => 'Soundbite has been successfully removed!', + ], + 'form' => [ + 'title' => 'New soundbite', + 'soundbite_title' => 'Soundbite title', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'submit' => 'Create soundbite', + ], + 'play' => 'Play soundbite', + 'stop' => 'Stop soundbite', + 'create' => 'New soundbite', + 'delete' => 'Delete soundbite', +]; diff --git a/modules/Admin/Language/eu/Validation.php b/modules/Admin/Language/eu/Validation.php new file mode 100644 index 00000000..f76c3163 --- /dev/null +++ b/modules/Admin/Language/eu/Validation.php @@ -0,0 +1,17 @@ + + '{field} is either not an image, or it is not wide or tall enough.', + 'is_image_ratio' => + '{field} is either not an image or not of the right ratio.', + 'is_json' => '{field} contains invalid JSON.', +]; diff --git a/modules/Admin/Language/eu/VideoClip.php b/modules/Admin/Language/eu/VideoClip.php new file mode 100644 index 00000000..638de697 --- /dev/null +++ b/modules/Admin/Language/eu/VideoClip.php @@ -0,0 +1,72 @@ + [ + 'title' => 'Video clips', + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Clip is waiting to be processed.', + 'pending' => 'pending', + 'pending_hint' => 'Clip will be generated shortly.', + 'running' => 'running', + 'running_hint' => 'Clip is being generated.', + 'failed' => 'failed', + 'failed_hint' => 'Clip could not be generated: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Clip was generated successfully!', + ], + 'clip' => 'Clip', + 'duration' => 'Job duration', + ], + 'title' => 'Video clip: {videoClipLabel}', + 'download_clip' => 'Download clip', + 'create' => 'New video clip', + 'go_to_page' => 'Go to clip page', + 'retry' => 'Retry clip generation', + 'delete' => 'Delete clip', + 'logs' => 'Job logs', + 'messages' => [ + 'alreadyExistingError' => 'The video clip you are trying to create already exists!', + 'addToQueueSuccess' => 'Video clip has been added to queue, awaiting to be created!', + 'deleteSuccess' => 'Video clip has been successfully removed!', + ], + 'format' => [ + 'landscape' => 'Landscape', + 'portrait' => 'Portrait', + 'squared' => 'Squared', + ], + 'form' => [ + 'title' => 'New video clip', + 'params_section_title' => 'Video clip parameters', + 'clip_title' => 'Clip title', + 'format' => [ + 'label' => 'Choose a format', + 'landscape_hint' => 'With a 16:9 ratio, landscape videos are great for PeerTube, Youtube and Vimeo.', + 'portrait_hint' => 'With a 9:16 ratio, portrait videos are great for TikTok, Youtube shorts and Instagram stories.', + 'squared_hint' => 'With a 1:1 ratio, squared videos are great for Mastodon, Facebook, Twitter and LinkedIn.', + ], + 'theme' => 'Select a theme', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'trim_start' => 'Trim start', + 'trim_end' => 'Trim end', + 'submit' => 'Create video clip', + ], + 'requirements' => [ + 'title' => 'Missing requirements', + 'missing' => 'You have missing requirements. Make sure to add all the required items to be allowed creating a video for this episode!', + 'ffmpeg' => 'FFmpeg', + 'gd' => 'Graphics Draw (GD)', + 'freetype' => 'Freetype library for GD', + 'transcript' => 'Transcript file (.srt)', + ], +]; diff --git a/modules/Admin/Language/fa/AboutCastopod.php b/modules/Admin/Language/fa/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/fa/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/fa/Breadcrumb.php b/modules/Admin/Language/fa/Breadcrumb.php index f3269bfa..408c9f9f 100644 --- a/modules/Admin/Language/fa/Breadcrumb.php +++ b/modules/Admin/Language/fa/Breadcrumb.php @@ -19,6 +19,7 @@ return [ 'pages' => 'pages', 'settings' => 'settings', 'theme' => 'theme', + 'about' => 'about', 'add' => 'add', 'new' => 'new', 'edit' => 'edit', @@ -28,15 +29,19 @@ return [ 'publish-date-edit' => 'edit publication date', 'unpublish' => 'unpublish', 'delete' => 'delete', + 'remove' => 'remove', 'fediverse' => 'fediverse', - 'block-lists' => 'block lists', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', 'users' => 'users', 'my-account' => 'my account', 'change-password' => 'change password', - 'import' => 'feed import', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', 'platforms' => 'platforms', 'social' => 'social networks', 'funding' => 'funding', + 'monetization-other' => 'other monetization', 'analytics' => 'analytics', 'locations' => 'locations', 'webpages' => 'web pages', diff --git a/modules/Admin/Language/fa/Charts.php b/modules/Admin/Language/fa/Charts.php index 4b33530e..6ede2510 100644 --- a/modules/Admin/Language/fa/Charts.php +++ b/modules/Admin/Language/fa/Charts.php @@ -37,4 +37,5 @@ return [ 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', 'total_storage_by_month' => 'Monthly storage (in MB)', 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', ]; diff --git a/modules/Admin/Language/fa/Common.php b/modules/Admin/Language/fa/Common.php index 596c8bcd..74addcf2 100644 --- a/modules/Admin/Language/fa/Common.php +++ b/modules/Admin/Language/fa/Common.php @@ -40,6 +40,7 @@ return [ ], 'upload_file' => 'Upload a file', 'remote_url' => 'Remote URL', + 'save' => 'Save', ], 'play_episode_button' => [ 'play' => 'Play', diff --git a/modules/Admin/Language/fa/Episode.php b/modules/Admin/Language/fa/Episode.php index 91313a7c..4fa846e3 100644 --- a/modules/Admin/Language/fa/Episode.php +++ b/modules/Admin/Language/fa/Episode.php @@ -22,6 +22,7 @@ return [ 'all_podcast_episodes' => 'All podcast episodes', 'back_to_podcast' => 'Go back to podcast', 'edit' => 'Edit', + 'preview' => 'Preview', 'publish' => 'Publish', 'publish_edit' => 'Edit publication', 'publish_date_edit' => 'Edit publication date', @@ -55,6 +56,7 @@ return [ }', 'episode' => 'Episode', 'visibility' => 'Visibility', + 'downloads' => 'Downloads', 'comments' => 'Comments', 'actions' => 'Actions', ], @@ -85,7 +87,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ @@ -137,9 +139,9 @@ return [ 'location_name' => 'Location name or address', 'location_name_hint' => 'This can be a real or fictional location', 'transcript' => 'Transcript (subtitles / closed captions)', - 'transcript_hint' => 'Only .srt are allowed.', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', 'transcript_download' => 'Download transcript', - 'transcript_file' => 'Transcript file (.srt)', + 'transcript_file' => 'Transcript file (.srt or .vtt)', 'transcript_remote_url' => 'Remote url for transcript', 'transcript_file_delete' => 'Delete transcript file', 'chapters' => 'Chapters', @@ -210,4 +212,14 @@ return [ 'light' => 'Light', 'light-transparent' => 'Light transparent', ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], ]; diff --git a/modules/Admin/Language/fa/Home.php b/modules/Admin/Language/fa/Home.php index 3ff4c04d..bc83fd98 100644 --- a/modules/Admin/Language/fa/Home.php +++ b/modules/Admin/Language/fa/Home.php @@ -9,6 +9,6 @@ declare(strict_types=1); */ return [ - 'all_podcasts' => 'All podcasts', - 'no_podcast' => 'No podcast found', + 'all_podcasts' => 'تمامی پادکست‌ها', + 'no_podcast' => 'هیچ پادکستی پیدا نشد', ]; diff --git a/modules/Admin/Language/fa/Install.php b/modules/Admin/Language/fa/Install.php index 36e373a2..a2643d24 100644 --- a/modules/Admin/Language/fa/Install.php +++ b/modules/Admin/Language/fa/Install.php @@ -9,46 +9,46 @@ declare(strict_types=1); */ return [ - 'manual_config' => 'Manual configuration', + 'manual_config' => 'پیکربندی دستی', 'manual_config_subtitle' => 'Create a `.env` file with your settings and refresh the page to continue installation.', 'form' => [ - 'instance_config' => 'Instance configuration', - 'hostname' => 'Hostname', - 'media_base_url' => 'Media base URL', + 'instance_config' => 'پیکربندی نمونه', + 'hostname' => 'نام میزبان', + 'media_base_url' => 'نشانی پایهٔ رسانه', 'media_base_url_hint' => 'If you use a CDN and/or an external analytics service, you may set them here.', - 'admin_gateway' => 'Admin gateway', + 'admin_gateway' => 'دروازهٔ مدیر', 'admin_gateway_hint' => 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', - 'auth_gateway' => 'Auth gateway', + 'auth_gateway' => 'دروازهٔ هویت‌سنجی', 'auth_gateway_hint' => 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', - 'database_config' => 'Database configuration', + 'database_config' => 'پیکربندی پایگاه داده', 'database_config_hint' => 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', - 'db_hostname' => 'Database hostname', - 'db_name' => 'Database name', - 'db_username' => 'Database username', - 'db_password' => 'Database password', - 'db_prefix' => 'Database prefix', + 'db_hostname' => 'نام میزبان پایگاه داده', + 'db_name' => 'نام پایگاه‌داده', + 'db_username' => 'نام کاربری پایگاه‌داده', + 'db_password' => 'گذرواژهٔ پایگاه‌داده', + 'db_prefix' => 'پيشوند پايگاه‌داده', 'db_prefix_hint' => "The prefix of the Castopod table names, leave as is if you don't know what it means.", - 'cache_config' => 'Cache configuration', + 'cache_config' => 'پیکربندی انباره', 'cache_config_hint' => 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', - 'cache_handler' => 'Cache handler', + 'cache_handler' => 'مدیر انباره', 'cacheHandlerOptions' => [ - 'file' => 'File', - 'redis' => 'Redis', + 'file' => 'پرونده', + 'redis' => 'ردیس', 'predis' => 'Predis', ], - 'next' => 'Next', - 'submit' => 'Finish install', - 'create_superadmin' => 'Create your superadmin account', - 'email' => 'Email', - 'username' => 'Username', - 'password' => 'Password', + 'next' => 'بعدی', + 'submit' => 'پایان نصب', + 'create_superadmin' => 'ایجاد حساب ابرمدیریتان', + 'email' => 'رایانامه', + 'username' => 'نام‌کاربری', + 'password' => 'گذرواژه', ], 'messages' => [ 'createSuperAdminSuccess' => diff --git a/modules/Admin/Language/fa/Navigation.php b/modules/Admin/Language/fa/Navigation.php index 68d4609d..f3ffb129 100644 --- a/modules/Admin/Language/fa/Navigation.php +++ b/modules/Admin/Language/fa/Navigation.php @@ -12,12 +12,14 @@ return [ 'toggle_sidebar' => 'Toggle sidebar', 'go_to_website' => 'Go to website', 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', 'dashboard' => 'Dashboard', 'admin' => 'Home', 'podcasts' => 'Podcasts', 'podcast-list' => 'All podcasts', 'podcast-create' => 'New podcast', - 'podcast-import' => 'Import a podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', 'persons' => 'Persons', 'person-list' => 'All persons', 'person-create' => 'New person', @@ -33,6 +35,7 @@ return [ 'settings' => 'Settings', 'settings-general' => 'General', 'settings-theme' => 'Theme', + 'admin-about' => 'About', 'account' => [ 'my-account' => 'My account', 'change-password' => 'Change password', diff --git a/modules/Admin/Language/fa/Person.php b/modules/Admin/Language/fa/Person.php index a652be9f..3465af96 100644 --- a/modules/Admin/Language/fa/Person.php +++ b/modules/Admin/Language/fa/Person.php @@ -9,57 +9,57 @@ declare(strict_types=1); */ return [ - 'persons' => 'Persons', - 'all_persons' => 'All persons', - 'no_person' => 'Nobody found!', - 'create' => 'Create a person', - 'view' => 'View person', - 'edit' => 'Edit person', - 'delete' => 'Delete person', + 'persons' => 'افراد', + 'all_persons' => 'تمامی افراد', + 'no_person' => 'هیچ‌کس پیدا نشد!', + 'create' => 'ایجاد یک نفر', + 'view' => 'دیدن فرد', + 'edit' => 'ویرایش فرد', + 'delete' => 'حذف فرد', 'messages' => [ - 'createSuccess' => 'Person has been successfully created!', - 'editSuccess' => 'Person has been successfully updated!', - 'deleteSuccess' => 'Person has been removed!', + 'createSuccess' => 'فرد با موفّقیت ساخته شد!', + 'editSuccess' => 'فرد با موفّقیت به‌روز شد!', + 'deleteSuccess' => 'فرد برداشته شد!', ], 'form' => [ - 'avatar' => 'Avatar', + 'avatar' => 'چهرک', 'avatar_size_hint' => - 'Avatar must be squared and at least 400px wide and tall.', - 'full_name' => 'Full name', + 'چهرک باید مربّعی بوده و کمینه ۴۰۰ پیکسل پنها و بلندا داشته باشد.', + 'full_name' => 'نام کامل', 'full_name_hint' => 'This is the full name or alias of the person.', - 'unique_name' => 'Unique name', - 'unique_name_hint' => 'Used for URLs', - 'information_url' => 'Information URL', + 'unique_name' => 'نام یکتا', + 'unique_name_hint' => 'استفاده شده برای نشانی‌ها', + 'information_url' => 'نشانی اطّلاعات', 'information_url_hint' => 'Url to a relevant resource of information about the person, such as a homepage or third-party profile platform.', - 'submit_create' => 'Create person', - 'submit_edit' => 'Save person', + 'submit_create' => 'ایجاد فرد', + 'submit_edit' => 'ذخیرهٔ فرد', ], 'podcast_form' => [ - 'title' => 'Manage persons', - 'add_section_title' => 'Add persons to this podcast', + 'title' => 'مدیریت افراد', + 'add_section_title' => 'افزودن افراد به این پادکست', 'add_section_subtitle' => 'You may pick several persons and roles.', - 'persons' => 'Persons', + 'persons' => 'افراد', 'persons_hint' => 'You may select one or several persons with the same roles. You need to create the persons first.', - 'roles' => 'Roles', + 'roles' => 'نقش‌ها', 'roles_hint' => 'You may select none, one or several roles for a person.', - 'submit_add' => 'Add person(s)', - 'remove' => 'Remove', + 'submit_add' => 'افزودن فرد(ها)', + 'remove' => 'برداشتن', ], 'episode_form' => [ - 'title' => 'Manage persons', - 'add_section_title' => 'Add persons to this episode', + 'title' => 'مدیریت افراد', + 'add_section_title' => 'افزودن افراد به این قسمت', 'add_section_subtitle' => 'You may pick several persons and roles.', - 'persons' => 'Persons', + 'persons' => 'افراد', 'persons_hint' => 'You may select one or several persons with the same roles. You need to create the persons first.', - 'roles' => 'Roles', + 'roles' => 'نقش‌ها', 'roles_hint' => 'You may select none, one or several roles for a person.', - 'submit_add' => 'Add person(s)', - 'remove' => 'Remove', + 'submit_add' => 'افزودن فرد(ها)', + 'remove' => 'برداشتن', ], - 'credits' => 'Credits', + 'credits' => 'اعتبارها', ]; diff --git a/modules/Admin/Language/fa/Platforms.php b/modules/Admin/Language/fa/Platforms.php index ab17d599..7dc10de7 100644 --- a/modules/Admin/Language/fa/Platforms.php +++ b/modules/Admin/Language/fa/Platforms.php @@ -9,22 +9,35 @@ declare(strict_types=1); */ return [ - 'title' => 'Platforms', - 'home_url' => 'Go to {platformName} website', - 'submit_url' => 'Submit your podcast on {platformName}', - 'visible' => 'Display in podcast homepage?', - 'on_embed' => 'Display on embeddable player?', - 'remove' => 'Remove {platformName}', - 'submit' => 'Save', + 'title' => [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', + 'home_url' => 'رفتن به پایگاه وب {platformName}', + 'register' => 'Register', + 'submit_url' => 'ثبت پادکستتان روی {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', + 'visible' => 'نمایش در صفحهٔ خانگی پادکست؟', + 'on_embed' => 'نمایش در پخش‌کنندهٔ تعبیه شده؟', + 'remove' => 'برداشتن {platformName}', + 'submit' => 'ذخیره', 'messages' => [ - 'updateSuccess' => 'Platform links have been successfully updated!', - 'removeLinkSuccess' => 'The platform link has been removed.', + 'updateSuccess' => 'پیوندهای بن‌سازه با موفّقیت به‌روز شدند!', + 'removeLinkSuccess' => 'پیوند بن‌سازه برداشته شد.', 'removeLinkError' => - 'The platform link could not be removed. Try again.', + 'پیوند بن‌سازه نتوانست برداشته شود. دوباره تلاش کنید.', ], 'description' => [ - 'podcasting' => 'The podcast ID on this platform', - 'social' => 'The podcast account ID on this platform', - 'funding' => 'Call to action message', + 'podcasting' => 'شناسهٔ پادکست روی این بن‌سازه', + 'social' => 'شناسهٔ حساب پادکست روی این بن‌سازه', + 'funding' => 'پیام فراخوانی کنش', ], ]; diff --git a/modules/Admin/Language/fa/Podcast.php b/modules/Admin/Language/fa/Podcast.php index 426b763b..9b1f047e 100644 --- a/modules/Admin/Language/fa/Podcast.php +++ b/modules/Admin/Language/fa/Podcast.php @@ -9,22 +9,24 @@ declare(strict_types=1); */ return [ - 'all_podcasts' => 'All podcasts', - 'no_podcast' => 'No podcast found!', - 'create' => 'Create podcast', - 'import' => 'Import podcast', - 'new_episode' => 'New Episode', - 'view' => 'View podcast', - 'edit' => 'Edit podcast', - 'publish' => 'Publish podcast', + 'all_podcasts' => 'تمامی پادکست‌ها', + 'no_podcast' => 'هیچ پادکستی پیدا نشد!', + 'create' => 'ایجاد پادکست', + 'import' => 'درون‌ریزی پادکست', + 'all_imports' => 'درون‌ریزی‌های پادکست', + 'new_episode' => 'قسمت جدید', + 'view' => 'دیدن پادکست', + 'edit' => 'ویرایش پادکست', + 'publish' => 'انتشار پادکست', 'publish_edit' => 'Edit publication', - 'delete' => 'Delete podcast', - 'see_episodes' => 'See episodes', - 'see_contributors' => 'See contributors', - 'go_to_page' => 'Go to page', - 'latest_episodes' => 'Latest episodes', - 'see_all_episodes' => 'See all episodes', - 'draft' => 'Draft', + 'delete' => 'حذف پادکست', + 'see_episodes' => 'دیدن قسمت‌ّا', + 'see_contributors' => 'دیدن مشارکت‌کنندگان', + 'monetization_other' => 'Other monetization', + 'go_to_page' => 'رفتن به صفحه', + 'latest_episodes' => 'جدیدترین قسمت‌ها', + 'see_all_episodes' => 'دیدن تمامی قسمت‌ها', + 'draft' => 'پیش‌نویس', 'messages' => [ 'createSuccess' => 'Podcast successfully created!', 'editSuccess' => 'Podcast has been successfully updated!', @@ -48,33 +50,44 @@ return [ other {# episodes were} } added to the podcast!', 'podcastFeedUpToDate' => 'Podcast is already up to date.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', 'publishError' => 'This podcast is either already published or scheduled for publication.', 'publishEditError' => 'This podcast is not scheduled for publication.', 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', 'scheduleDateError' => 'Schedule date must be set!', ], 'form' => [ - 'identity_section_title' => 'Podcast identity', + 'identity_section_title' => 'هویت پادکست', 'identity_section_subtitle' => 'These fields allow you to get noticed.', - 'cover' => 'Podcast cover', - 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', - 'banner' => 'Podcast banner', + 'fediverse_section_title' => 'Fediverse identity', + + 'cover' => 'جلد پادکست', + 'cover_size_hint' => 'جلد باید مربّعی بوده و کمینه ۱۴۰۰ پیکسل پنها و بلندا داشته باشد.', + 'banner' => 'بیرق پادکست', 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', - 'banner_delete' => 'Delete podcast banner', - 'title' => 'Title', - 'handle' => 'Handle', + 'banner_delete' => 'حذف بیرق پادکست', + 'title' => 'عنوان', + 'handle' => 'شناسه', 'handle_hint' => 'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.', 'type' => [ - 'label' => 'Type', - 'episodic' => 'Episodic', + 'label' => 'گونه', + 'episodic' => 'قسمتی', 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', - 'serial' => 'Serial', - 'serial_hint' => 'If episodes are intended to be consumed in sequential order. The oldest episodes will be presented first.', + 'serial' => 'سریالی', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', ], - 'description' => 'Description', - 'classification_section_title' => 'Classification', + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + ], + 'description' => 'شرح', + 'classification_section_title' => 'طبقه‌بندی', 'classification_section_subtitle' => 'These fields will impact your audience and competition.', 'language' => 'Language', @@ -96,6 +109,8 @@ return [ 'owner_email' => 'Owner email', 'owner_email_hint' => 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', 'publisher' => 'Publisher', 'publisher_hint' => 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', @@ -110,6 +125,11 @@ return [ 'premium' => 'Premium', 'premium_by_default' => 'Episodes must be set as premium by default', 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', 'payment_pointer' => 'Payment Pointer for Web Monetization', 'payment_pointer_hint' => 'This is your where you will receive money thanks to Web Monetization', @@ -118,11 +138,12 @@ return [ 'If you need RSS tags that Castopod does not handle, set them here.', 'custom_rss' => 'Custom RSS tags for the podcast', 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', 'new_feed_url' => 'New feed URL', 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', 'partnership' => 'Partnership', 'partner_id' => 'ID', 'partner_link_url' => 'Link URL', @@ -130,7 +151,6 @@ return [ 'partner_id_hint' => 'Your own partner ID', 'partner_link_url_hint' => 'The generic partner link address', 'partner_image_url_hint' => 'The generic partner image address', - 'status_section_title' => 'Status', 'block' => 'Podcast should be hidden from public catalogues', 'block_hint' => 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', diff --git a/modules/Admin/Language/fa/PodcastImport.php b/modules/Admin/Language/fa/PodcastImport.php deleted file mode 100644 index 7c3ef67d..00000000 --- a/modules/Admin/Language/fa/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'The podcast to import', - 'old_podcast_section_subtitle' => - 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', - 'imported_feed_url' => 'Feed URL', - 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', - 'new_podcast_section_title' => 'The new podcast', - 'advanced_params_section_title' => 'Advanced parameters', - 'advanced_params_section_subtitle' => - 'Keep the default values if you have no idea of what the fields are for.', - 'slug_field' => 'Field to be used to calculate episode slug', - 'description_field' => - 'Source field used for episode description / show notes', - 'force_renumber' => 'Force episodes renumbering', - 'force_renumber_hint' => - 'Use this if your podcast does not have episode numbers but wish to set them during import.', - 'season_number' => 'Season number', - 'season_number_hint' => - 'Use this if your podcast does not have a season number but wish to set one during import. Leave blank otherwise.', - 'max_episodes' => 'Maximum number of episodes to import', - 'max_episodes_hint' => 'Leave blank to import all episodes', - 'lock_import' => - 'This feed is protected. You cannot import it. If you are the owner, unprotect it on the origin platform.', - 'submit' => 'Import podcast', -]; diff --git a/modules/Admin/Language/fa/PodcastNavigation.php b/modules/Admin/Language/fa/PodcastNavigation.php index b4d7ddc0..bb777707 100644 --- a/modules/Admin/Language/fa/PodcastNavigation.php +++ b/modules/Admin/Language/fa/PodcastNavigation.php @@ -10,10 +10,13 @@ declare(strict_types=1); return [ 'go_to_page' => 'Go to podcast page', + 'rss_feed' => 'RSS feed', 'dashboard' => 'Podcast dashboard', 'podcast-view' => 'Home', 'podcast-edit' => 'Edit podcast', 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', 'episodes' => 'Episodes', 'episode-list' => 'All episodes', 'episode-create' => 'New episode', @@ -25,14 +28,15 @@ return [ 'podcast-analytics-players' => 'Players', 'podcast-analytics-listening-time' => 'Listening time', 'podcast-analytics-time-periods' => 'Time periods', - 'premium' => 'Premium', + 'monetization' => 'Monetization', 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', + 'subscription-create' => 'Add subscription', 'contributors' => 'Contributors', 'contributor-list' => 'All contributors', 'contributor-add' => 'Add contributor', - 'platforms' => 'External platforms', - 'platforms-podcasting' => 'Podcasting', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', 'platforms-social' => 'Social networks', - 'platforms-funding' => 'Funding', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', ]; diff --git a/modules/Admin/Language/fa/Validation.php b/modules/Admin/Language/fa/Validation.php index 750b1968..0659cd86 100644 --- a/modules/Admin/Language/fa/Validation.php +++ b/modules/Admin/Language/fa/Validation.php @@ -10,9 +10,8 @@ declare(strict_types=1); return [ 'min_dims' => - '{field} is either not an image, or it is not wide or tall enough.', + '{field} تصویر نبوده یا پنها و بلندایش کافی نیست.', 'is_image_ratio' => - '{field} is either not an image or not of the right ratio.', - 'validate_url' => - 'The {field} field must be a valid URL (eg. https://example.com/).', + '{field} تصویر نبوده یا ابعادش درست نیست.', + 'is_json' => '{field} contains invalid JSON.', ]; diff --git a/modules/Admin/Language/fr-ca/AboutCastopod.php b/modules/Admin/Language/fr-ca/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/fr-ca/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/fr-ca/Breadcrumb.php b/modules/Admin/Language/fr-ca/Breadcrumb.php new file mode 100644 index 00000000..408c9f9f --- /dev/null +++ b/modules/Admin/Language/fr-ca/Breadcrumb.php @@ -0,0 +1,57 @@ + 'breadcrumb', + config('Admin') + ->gateway => 'Home', + 'podcasts' => 'podcasts', + 'episodes' => 'episodes', + 'subscriptions' => 'subscriptions', + 'contributors' => 'contributors', + 'pages' => 'pages', + 'settings' => 'settings', + 'theme' => 'theme', + 'about' => 'about', + 'add' => 'add', + 'new' => 'new', + 'edit' => 'edit', + 'persons' => 'persons', + 'publish' => 'publish', + 'publish-edit' => 'edit publication', + 'publish-date-edit' => 'edit publication date', + 'unpublish' => 'unpublish', + 'delete' => 'delete', + 'remove' => 'remove', + 'fediverse' => 'fediverse', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', + 'users' => 'users', + 'my-account' => 'my account', + 'change-password' => 'change password', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', + 'platforms' => 'platforms', + 'social' => 'social networks', + 'funding' => 'funding', + 'monetization-other' => 'other monetization', + 'analytics' => 'analytics', + 'locations' => 'locations', + 'webpages' => 'web pages', + 'unique-listeners' => 'unique listeners', + 'players' => 'players', + 'listening-time' => 'listening time', + 'time-periods' => 'time periods', + 'soundbites' => 'soundbites', + 'video-clips' => 'video clips', + 'embed' => 'embeddable player', + 'notifications' => 'notifications', + 'suspend' => 'suspend', +]; diff --git a/modules/Admin/Language/fr-ca/Charts.php b/modules/Admin/Language/fr-ca/Charts.php new file mode 100644 index 00000000..6ede2510 --- /dev/null +++ b/modules/Admin/Language/fr-ca/Charts.php @@ -0,0 +1,41 @@ + 'Episode downloads by service (for the past week)', + 'by_player_weekly' => 'Episode downloads by player (for the past week)', + 'by_player_yearly' => 'Episode downloads by player (for the past year)', + 'by_device_weekly' => 'Episode downloads by device (for the past week)', + 'by_os_weekly' => 'Episode downloads by O.S. (for the past week)', + 'podcast_by_region' => 'Episode downloads by region (for the past week)', + 'unique_daily_listeners' => 'Daily unique listeners', + 'unique_monthly_listeners' => 'Monthly unique listeners', + 'by_browser' => 'Web pages usage by browser (for the past week)', + 'podcast_by_day' => 'Episode daily downloads', + 'podcast_by_month' => 'Episode monthly downloads', + 'episode_by_day' => 'Episode daily downloads (first 60 days)', + 'episode_by_month' => 'Episode monthly downloads', + 'episodes_by_day' => + '5 latest episodes downloads (during their first 60 days)', + 'by_country_weekly' => 'Episode downloads by country (for the past week)', + 'by_country_yearly' => 'Episode downloads by country (for the past year)', + 'by_domain_weekly' => 'Web pages visits by source (for the past week)', + 'by_domain_yearly' => 'Web pages visits by source (for the past year)', + 'by_entry_page' => 'Web pages visits by landing page (for the past week)', + 'podcast_bots' => 'Bots (crawlers)', + 'daily_listening_time' => 'Daily cumulative listening time', + 'monthly_listening_time' => 'Monthly cumulative listening time', + 'by_weekday' => 'By week day (for the past 60 days)', + 'by_hour' => 'By time of day (for the past 60 days)', + 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', + 'total_storage_by_month' => 'Monthly storage (in MB)', + 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', +]; diff --git a/modules/Admin/Language/fr-ca/Common.php b/modules/Admin/Language/fr-ca/Common.php new file mode 100644 index 00000000..74addcf2 --- /dev/null +++ b/modules/Admin/Language/fr-ca/Common.php @@ -0,0 +1,52 @@ + 'Yes', + 'no' => 'No', + 'cancel' => 'Cancel', + 'optional' => 'Optional', + 'more' => 'More', + 'no_data' => 'No data found!', + 'close' => 'Close', + 'edit' => 'Edit', + 'copy' => 'Copy', + 'copied' => 'Copied!', + 'home' => 'Home', + 'explicit' => 'Explicit', + 'powered_by' => 'Powered by {castopod}', + 'actions' => 'Actions', + 'pageInfo' => 'Page {currentPage} out of {pageCount}', + 'go_back' => 'Go back', + 'forms' => [ + 'editor' => [ + 'write' => 'Write', + 'preview' => 'Preview', + 'help' => 'Powered by markdown', + ], + 'multiSelect' => [ + 'selectText' => 'Press to select', + 'loadingText' => 'Loading…', + 'noResultsText' => 'No results found', + 'noChoicesText' => 'No choices to choose from', + 'maxItemText' => 'Cannot add more items', + ], + 'upload_file' => 'Upload a file', + 'remote_url' => 'Remote URL', + 'save' => 'Save', + ], + 'play_episode_button' => [ + 'play' => 'Play', + 'playing' => 'Playing', + ], + 'size_limit' => 'Size limit: {0}.', + 'choose_interact' => 'Choose how to interact', + 'view' => 'View', +]; diff --git a/modules/Admin/Language/fr-ca/Countries.php b/modules/Admin/Language/fr-ca/Countries.php new file mode 100644 index 00000000..4cd5d9c8 --- /dev/null +++ b/modules/Admin/Language/fr-ca/Countries.php @@ -0,0 +1,264 @@ + 'Andorra', + 'AE' => 'United Arab Emirates', + 'AF' => 'Afghanistan', + 'AG' => 'Antigua and Barbuda', + 'AI' => 'Anguilla', + 'AL' => 'Albania', + 'AM' => 'Armenia', + 'AO' => 'Angola', + 'AQ' => 'Antarctica', + 'AR' => 'Argentina', + 'AS' => 'American Samoa', + 'AT' => 'Austria', + 'AU' => 'Australia', + 'AW' => 'Aruba', + 'AX' => 'Åland Islands', + 'AZ' => 'Azerbaijan', + 'BA' => 'Bosnia and Herzegovina', + 'BB' => 'Barbados', + 'BD' => 'Bangladesh', + 'BE' => 'Belgium', + 'BF' => 'Burkina Faso', + 'BG' => 'Bulgaria', + 'BH' => 'Bahrain', + 'BI' => 'Burundi', + 'BJ' => 'Benin', + 'BL' => 'Saint Barthélemy', + 'BM' => 'Bermuda', + 'BN' => 'Brunei Darussalam', + 'BO' => 'Bolivia, Plurinational State of', + 'BQ' => 'Bonaire, Sint Eustatius and Saba', + 'BR' => 'Brazil', + 'BS' => 'Bahamas', + 'BT' => 'Bhutan', + 'BV' => 'Bouvet Island', + 'BW' => 'Botswana', + 'BY' => 'Belarus', + 'BZ' => 'Belize', + 'CA' => 'Canada', + 'CC' => 'Cocos (Keeling) Islands', + 'CD' => 'Congo, the Democratic Republic of the', + 'CF' => 'Central African Republic', + 'CG' => 'Congo', + 'CH' => 'Switzerland', + 'CI' => "Côte d'Ivoire", + 'CK' => 'Cook Islands', + 'CL' => 'Chile', + 'CM' => 'Cameroon', + 'CN' => 'China', + 'CO' => 'Colombia', + 'CR' => 'Costa Rica', + 'CU' => 'Cuba', + 'CV' => 'Cape Verde', + 'CW' => 'Curaçao', + 'CX' => 'Christmas Island', + 'CY' => 'Cyprus', + 'CZ' => 'Czech Republic', + 'DE' => 'Germany', + 'DJ' => 'Djibouti', + 'DK' => 'Denmark', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'DZ' => 'Algeria', + 'EC' => 'Ecuador', + 'EE' => 'Estonia', + 'EG' => 'Egypt', + 'EH' => 'Western Sahara', + 'ER' => 'Eritrea', + 'ES' => 'Spain', + 'ET' => 'Ethiopia', + 'FI' => 'Finland', + 'FJ' => 'Fiji', + 'FK' => 'Falkland Islands (Malvinas)', + 'FM' => 'Micronesia, Federated States of', + 'FO' => 'Faroe Islands', + 'FR' => 'France', + 'GA' => 'Gabon', + 'GB' => 'United Kingdom', + 'GD' => 'Grenada', + 'GE' => 'Georgia', + 'GF' => 'French Guiana', + 'GG' => 'Guernsey', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GL' => 'Greenland', + 'GM' => 'Gambia', + 'GN' => 'Guinea', + 'GP' => 'Guadeloupe', + 'GQ' => 'Equatorial Guinea', + 'GR' => 'Greece', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'GT' => 'Guatemala', + 'GU' => 'Guam', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HK' => 'Hong Kong', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HR' => 'Croatia', + 'HT' => 'Haiti', + 'HU' => 'Hungary', + 'ID' => 'Indonesia', + 'IE' => 'Ireland', + 'IL' => 'Israel', + 'IM' => 'Isle of Man', + 'IN' => 'India', + 'IO' => 'British Indian Ocean Territory', + 'IQ' => 'Iraq', + 'IR' => 'Iran, Islamic Republic of', + 'IS' => 'Iceland', + 'IT' => 'Italy', + 'JE' => 'Jersey', + 'JM' => 'Jamaica', + 'JO' => 'Jordan', + 'JP' => 'Japan', + 'KE' => 'Kenya', + 'KG' => 'Kyrgyzstan', + 'KH' => 'Cambodia', + 'KI' => 'Kiribati', + 'KM' => 'Comoros', + 'KN' => 'Saint Kitts and Nevis', + 'KP' => "Korea, Democratic People's Republic of", + 'KR' => 'Korea, Republic of', + 'KW' => 'Kuwait', + 'KY' => 'Cayman Islands', + 'KZ' => 'Kazakhstan', + 'LA' => "Lao People's Democratic Republic", + 'LB' => 'Lebanon', + 'LC' => 'Saint Lucia', + 'LI' => 'Liechtenstein', + 'LK' => 'Sri Lanka', + 'LR' => 'Liberia', + 'LS' => 'Lesotho', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'LV' => 'Latvia', + 'LY' => 'Libya', + 'MA' => 'Morocco', + 'MC' => 'Monaco', + 'MD' => 'Moldova, Republic of', + 'ME' => 'Montenegro', + 'MF' => 'Saint Martin (French part)', + 'MG' => 'Madagascar', + 'MH' => 'Marshall Islands', + 'MK' => 'Macedonia, the Former Yugoslav Republic of', + 'ML' => 'Mali', + 'MM' => 'Myanmar', + 'MN' => 'Mongolia', + 'MO' => 'Macao', + 'MP' => 'Northern Mariana Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MS' => 'Montserrat', + 'MT' => 'Malta', + 'MU' => 'Mauritius', + 'MV' => 'Maldives', + 'MW' => 'Malawi', + 'MX' => 'Mexico', + 'MY' => 'Malaysia', + 'MZ' => 'Mozambique', + 'N/A' => 'Not Applicable (local IP…)', + 'NA' => 'Namibia', + 'NC' => 'New Caledonia', + 'NE' => 'Niger', + 'NF' => 'Norfolk Island', + 'NG' => 'Nigeria', + 'NI' => 'Nicaragua', + 'NL' => 'Netherlands', + 'NO' => 'Norway', + 'NP' => 'Nepal', + 'NR' => 'Nauru', + 'NU' => 'Niue', + 'NZ' => 'New Zealand', + 'OM' => 'Oman', + 'PA' => 'Panama', + 'PE' => 'Peru', + 'PF' => 'French Polynesia', + 'PG' => 'Papua New Guinea', + 'PH' => 'Philippines', + 'PK' => 'Pakistan', + 'PL' => 'Poland', + 'PM' => 'Saint Pierre and Miquelon', + 'PN' => 'Pitcairn', + 'PR' => 'Puerto Rico', + 'PS' => 'Palestine, State of', + 'PT' => 'Portugal', + 'PW' => 'Palau', + 'PY' => 'Paraguay', + 'QA' => 'Qatar', + 'RE' => 'Réunion', + 'RO' => 'Romania', + 'RS' => 'Serbia', + 'RU' => 'Russian Federation', + 'RW' => 'Rwanda', + 'SA' => 'Saudi Arabia', + 'SB' => 'Solomon Islands', + 'SC' => 'Seychelles', + 'SD' => 'Sudan', + 'SE' => 'Sweden', + 'SG' => 'Singapore', + 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', + 'SI' => 'Slovenia', + 'SJ' => 'Svalbard and Jan Mayen', + 'SK' => 'Slovakia', + 'SL' => 'Sierra Leone', + 'SM' => 'San Marino', + 'SN' => 'Senegal', + 'SO' => 'Somalia', + 'SR' => 'Suriname', + 'SS' => 'South Sudan', + 'ST' => 'Sao Tome and Principe', + 'SV' => 'El Salvador', + 'SX' => 'Sint Maarten (Dutch part)', + 'SY' => 'Syrian Arab Republic', + 'SZ' => 'Swaziland', + 'TC' => 'Turks and Caicos Islands', + 'TD' => 'Chad', + 'TF' => 'French Southern Territories', + 'TG' => 'Togo', + 'TH' => 'Thailand', + 'TJ' => 'Tajikistan', + 'TK' => 'Tokelau', + 'TL' => 'Timor-Leste', + 'TM' => 'Turkmenistan', + 'TN' => 'Tunisia', + 'TO' => 'Tonga', + 'TR' => 'Turkey', + 'TT' => 'Trinidad and Tobago', + 'TV' => 'Tuvalu', + 'TW' => 'Taiwan, Province of China', + 'TZ' => 'Tanzania, United Republic of', + 'UA' => 'Ukraine', + 'UG' => 'Uganda', + 'UM' => 'United States Minor Outlying Islands', + 'US' => 'United States', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VA' => 'Holy See (Vatican City State)', + 'VC' => 'Saint Vincent and the Grenadines', + 'VE' => 'Venezuela, Bolivarian Republic of', + 'VG' => 'Virgin Islands, British', + 'VI' => 'Virgin Islands, U.S.', + 'VN' => 'Viet Nam', + 'VU' => 'Vanuatu', + 'WF' => 'Wallis and Futuna', + 'WS' => 'Samoa', + 'YE' => 'Yemen', + 'YT' => 'Mayotte', + 'ZA' => 'South Africa', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', +]; diff --git a/modules/Admin/Language/fr-ca/Dashboard.php b/modules/Admin/Language/fr-ca/Dashboard.php new file mode 100644 index 00000000..881073fd --- /dev/null +++ b/modules/Admin/Language/fr-ca/Dashboard.php @@ -0,0 +1,28 @@ + 'Admin dashboard', + 'welcome_message' => 'Welcome to the admin area!', + 'podcasts' => [ + 'title' => 'Podcasts', + 'not_found' => 'No published podcast', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'episodes' => [ + 'title' => 'Episodes', + 'not_found' => 'No published episode', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'storage' => [ + 'title' => 'Storage', + 'subtitle' => '{totalUploaded} out of {totalStorage}', + ], +]; diff --git a/modules/Admin/Language/fr-ca/Episode.php b/modules/Admin/Language/fr-ca/Episode.php new file mode 100644 index 00000000..4fa846e3 --- /dev/null +++ b/modules/Admin/Language/fr-ca/Episode.php @@ -0,0 +1,225 @@ + 'Season {seasonNumber}', + 'season_abbr' => 'S{seasonNumber}', + 'number' => 'Episode {episodeNumber}', + 'number_abbr' => 'Ep. {episodeNumber}', + 'season_episode' => 'Season {seasonNumber} episode {episodeNumber}', + 'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}', + 'number_of_comments' => '{numberOfComments, plural, + one {# comment} + other {# comments} + }', + 'all_podcast_episodes' => 'All podcast episodes', + 'back_to_podcast' => 'Go back to podcast', + 'edit' => 'Edit', + 'preview' => 'Preview', + 'publish' => 'Publish', + 'publish_edit' => 'Edit publication', + 'publish_date_edit' => 'Edit publication date', + 'unpublish' => 'Unpublish', + 'publish_error' => 'Episode is already published.', + 'publish_edit_error' => 'Episode is already published.', + 'publish_cancel_error' => 'Episode is already published.', + 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', + 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', + 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'unpublish_error' => 'Episode is not published.', + 'delete' => 'Delete', + 'go_to_page' => 'Go to page', + 'create' => 'Add an episode', + 'publication_status' => [ + 'published' => 'Published', + 'with_podcast' => 'Published', + 'scheduled' => 'Scheduled', + 'not_published' => 'Not published', + ], + 'with_podcast_hint' => 'To be published at the same time as the podcast', + 'list' => [ + 'search' => [ + 'placeholder' => 'Search for an episode', + 'clear' => 'Clear search', + 'submit' => 'Search', + ], + 'number_of_episodes' => '{numberOfEpisodes, plural, + one {# episode} + other {# episodes} + }', + 'episode' => 'Episode', + 'visibility' => 'Visibility', + 'downloads' => 'Downloads', + 'comments' => 'Comments', + 'actions' => 'Actions', + ], + 'messages' => [ + 'createSuccess' => 'Episode has been successfully created!', + 'editSuccess' => 'Episode has been successfully updated!', + 'publishSuccess' => '{publication_status, select, + published {Episode successfully published!} + scheduled {Episode publication successfully scheduled!} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not published.} + }', + 'publishCancelSuccess' => 'Episode publication successfully cancelled!', + 'unpublishBeforeDeleteTip' => 'You must unpublish the episode before deleting it.', + 'scheduleDateError' => 'Schedule date must be set!', + 'deletePublishedEpisodeError' => 'Please unpublish the episode before deleting it.', + 'deleteSuccess' => 'Episode successfully deleted!', + 'deleteError' => 'Failed to delete episode {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deleteFileError' => 'Failed to delete {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + } file {file_key}. You may manually remove it from your disk.', + 'sameSlugError' => 'An episode with the chosen slug already exists.', + ], + 'form' => [ + 'file_size_error' => + 'Your file size is too big! Max size is {0}. Increase the `memory_limit`, `upload_max_filesize` and `post_max_size` values in your php configuration file then restart your web server to upload your file.', + 'audio_file' => 'Audio file', + 'audio_file_hint' => 'Choose an .mp3 or .m4a audio file.', + 'info_section_title' => 'Episode info', + 'cover' => 'Episode cover', + 'cover_hint' => + 'If you do not set a cover, the podcast cover will be used instead.', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'title' => 'Title', + 'title_hint' => + 'Should contain a clear and concise episode name. Do not specify the episode or season numbers here.', + 'permalink' => 'Permalink', + 'season_number' => 'Season', + 'episode_number' => 'Episode', + 'type' => [ + 'label' => 'Type', + 'full' => 'Full', + 'full_hint' => 'Complete content (the episode)', + 'trailer' => 'Trailer', + 'trailer_hint' => 'Short, promotional piece of content that represents a preview of the current show', + 'bonus' => 'Bonus', + 'bonus_hint' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show', + ], + 'premium_title' => 'Premium', + 'premium' => 'Episode must be accessible to premium subscribers only', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does the episode contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'show_notes_section_title' => 'Show notes', + 'show_notes_section_subtitle' => + 'Up to 4000 characters, be clear and concise. Show notes help potential listeners in finding the episode.', + 'description' => 'Description', + 'description_footer' => 'Description footer', + 'description_footer_hint' => + 'This text is added at the end of each episode description, it is a good place to input your social links for example.', + 'additional_files_section_title' => 'Additional files', + 'additional_files_section_subtitle' => + 'These files may be used by other platforms to provide better experience to your audience. See the {podcastNamespaceLink} for more information.', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this episode about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real or fictional location', + 'transcript' => 'Transcript (subtitles / closed captions)', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', + 'transcript_download' => 'Download transcript', + 'transcript_file' => 'Transcript file (.srt or .vtt)', + 'transcript_remote_url' => 'Remote url for transcript', + 'transcript_file_delete' => 'Delete transcript file', + 'chapters' => 'Chapters', + 'chapters_hint' => 'File must be in JSON Chapters format.', + 'chapters_download' => 'Download chapters', + 'chapters_file' => 'Chapters file', + 'chapters_remote_url' => 'Remote url for chapters file', + 'chapters_file_delete' => 'Delete chapters file', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the episode', + 'custom_rss_hint' => 'This will be injected within the ❬item❭ tag.', + 'block' => 'Episode should be hidden from public catalogues', + 'block_hint' => + 'The episode show or hide status: toggling this on prevents the episode from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'submit_create' => 'Create episode', + 'submit_edit' => 'Save episode', + ], + 'publish_form' => [ + 'back_to_episode_dashboard' => 'Back to episode dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your episode. The message will be broadcasted to all your followers in the fediverse and be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + 'with_podcast' => 'Publish alongside podcast', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_clear' => 'Clear publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the episode release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit' => 'Publish', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your episode.', + 'message_warning_submit' => 'Publish anyways', + ], + 'publish_date_edit_form' => [ + 'new_publication_date' => 'New publication date', + 'new_publication_date_hint' => 'Must be set to a past date.', + 'submit' => 'Edit publication date', + ], + 'unpublish_form' => [ + 'disclaimer' => + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", + 'understand' => 'I understand, I want to unpublish the episode', + 'submit' => 'Unpublish', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.", + 'understand' => 'I understand, I want to delete the episode', + 'submit' => 'Delete', + ], + 'embed' => [ + 'title' => 'Embeddable player', + 'label' => + 'Pick a theme color, copy the embeddable player to clipboard, then paste it on your website.', + 'clipboard_iframe' => 'Copy embeddable player to clipboard', + 'clipboard_url' => 'Copy address to clipboard', + 'dark' => 'Dark', + 'dark-transparent' => 'Dark transparent', + 'light' => 'Light', + 'light-transparent' => 'Light transparent', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], +]; diff --git a/modules/Admin/Language/fr-ca/EpisodeNavigation.php b/modules/Admin/Language/fr-ca/EpisodeNavigation.php new file mode 100644 index 00000000..1406e301 --- /dev/null +++ b/modules/Admin/Language/fr-ca/EpisodeNavigation.php @@ -0,0 +1,23 @@ + 'View episode page', + 'dashboard' => 'Episode dashboard', + 'episode-view' => 'Home', + 'episode-edit' => 'Edit episode', + 'episode-persons-manage' => 'Manage persons', + 'embed-add' => 'Embeddable player', + 'clips' => 'Clips', + 'video-clips-list' => 'Video clips', + 'video-clips-create' => 'New video clip', + 'soundbites-list' => 'Soundbites', + 'soundbites-create' => 'New soundbite', +]; diff --git a/modules/Admin/Language/fr-ca/Fediverse.php b/modules/Admin/Language/fr-ca/Fediverse.php new file mode 100644 index 00000000..0e4ca66d --- /dev/null +++ b/modules/Admin/Language/fr-ca/Fediverse.php @@ -0,0 +1,32 @@ + [ + 'actorNotFound' => 'The account could not be found!', + 'blockActorSuccess' => '{actor} has been blocked!', + 'unblockActorSuccess' => 'Actor has been unblocked!', + 'blockDomainSuccess' => '{domain} has been blocked!', + 'unblockDomainSuccess' => '{domain} has been unblocked!', + ], + 'blocked_actors' => 'Blocked accounts', + 'blocked_domains' => 'Blocked domains', + 'block_lists_form' => [ + 'handle' => 'Account handle', + 'handle_hint' => 'Input @username@domain account.', + 'domain' => 'Domain name', + 'submit' => 'Block!', + ], + 'list' => [ + 'actor' => 'Account', + 'domain' => 'Domain name', + 'unblock' => 'Unblock', + ], +]; diff --git a/modules/Admin/Language/fr-ca/Home.php b/modules/Admin/Language/fr-ca/Home.php new file mode 100644 index 00000000..3ff4c04d --- /dev/null +++ b/modules/Admin/Language/fr-ca/Home.php @@ -0,0 +1,14 @@ + 'All podcasts', + 'no_podcast' => 'No podcast found', +]; diff --git a/modules/Admin/Language/fr-ca/Install.php b/modules/Admin/Language/fr-ca/Install.php new file mode 100644 index 00000000..36e373a2 --- /dev/null +++ b/modules/Admin/Language/fr-ca/Install.php @@ -0,0 +1,61 @@ + 'Manual configuration', + 'manual_config_subtitle' => + 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'form' => [ + 'instance_config' => 'Instance configuration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', + 'db_hostname' => 'Database hostname', + 'db_name' => 'Database name', + 'db_username' => 'Database username', + 'db_password' => 'Database password', + 'db_prefix' => 'Database prefix', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Cache handler', + 'cacheHandlerOptions' => [ + 'file' => 'File', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Next', + 'submit' => 'Finish install', + 'create_superadmin' => 'Create your superadmin account', + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Your superadmin account has been created successfully. Login to start podcasting!', + 'databaseConnectError' => + 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'writeError' => + "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + ], +]; diff --git a/modules/Admin/Language/fr-ca/Navigation.php b/modules/Admin/Language/fr-ca/Navigation.php new file mode 100644 index 00000000..f3ffb129 --- /dev/null +++ b/modules/Admin/Language/fr-ca/Navigation.php @@ -0,0 +1,44 @@ + 'Toggle sidebar', + 'go_to_website' => 'Go to website', + 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', + 'dashboard' => 'Dashboard', + 'admin' => 'Home', + 'podcasts' => 'Podcasts', + 'podcast-list' => 'All podcasts', + 'podcast-create' => 'New podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', + 'persons' => 'Persons', + 'person-list' => 'All persons', + 'person-create' => 'New person', + 'fediverse' => 'Fediverse', + 'fediverse-blocked-actors' => 'Blocked accounts', + 'fediverse-blocked-domains' => 'Blocked domains', + 'users' => 'Users', + 'user-list' => 'All users', + 'user-create' => 'New user', + 'pages' => 'Pages', + 'page-list' => 'All pages', + 'page-create' => 'New Page', + 'settings' => 'Settings', + 'settings-general' => 'General', + 'settings-theme' => 'Theme', + 'admin-about' => 'About', + 'account' => [ + 'my-account' => 'My account', + 'change-password' => 'Change password', + 'logout' => 'Logout', + ], +]; diff --git a/modules/Admin/Language/fr-ca/Notifications.php b/modules/Admin/Language/fr-ca/Notifications.php new file mode 100644 index 00000000..2b139d51 --- /dev/null +++ b/modules/Admin/Language/fr-ca/Notifications.php @@ -0,0 +1,19 @@ + 'Notifications', + 'reply' => '{actor_username} replied to your post', + 'favourite' => '{actor_username} favourited your post', + 'reblog' => '{actor_username} shared your post', + 'follow' => '{actor_username} started following you', + 'no_notifications' => 'No notifications', + 'mark_all_as_read' => 'Mark all as read', +]; diff --git a/modules/Admin/Language/fr-ca/Page.php b/modules/Admin/Language/fr-ca/Page.php new file mode 100644 index 00000000..b6f49de5 --- /dev/null +++ b/modules/Admin/Language/fr-ca/Page.php @@ -0,0 +1,30 @@ + 'Back to home', + 'page' => 'Page', + 'all_pages' => 'All pages', + 'create' => 'New page', + 'go_to_page' => 'Go to page', + 'edit' => 'Edit page', + 'delete' => 'Delete page', + 'form' => [ + 'title' => 'Title', + 'permalink' => 'Permalink', + 'content' => 'Content', + 'submit_create' => 'Create page', + 'submit_edit' => 'Save', + ], + 'messages' => [ + 'createSuccess' => 'The page “{pageTitle}” was created successfully!', + 'editSuccess' => 'The page was successfully updated!', + ], +]; diff --git a/modules/Admin/Language/fr-ca/Pager.php b/modules/Admin/Language/fr-ca/Pager.php new file mode 100644 index 00000000..e25ee638 --- /dev/null +++ b/modules/Admin/Language/fr-ca/Pager.php @@ -0,0 +1,21 @@ + 'Page navigation', + 'first' => 'First', + 'previous' => 'Previous', + 'next' => 'Next', + 'last' => 'Last', + 'older' => 'Older', + 'newer' => 'Newer', + 'invalidTemplate' => '{0} is not a valid Pager template.', + 'invalidPaginationGroup' => '{0} is not a valid Pagination group.', +]; diff --git a/modules/Admin/Language/fr-ca/Person.php b/modules/Admin/Language/fr-ca/Person.php new file mode 100644 index 00000000..a652be9f --- /dev/null +++ b/modules/Admin/Language/fr-ca/Person.php @@ -0,0 +1,65 @@ + 'Persons', + 'all_persons' => 'All persons', + 'no_person' => 'Nobody found!', + 'create' => 'Create a person', + 'view' => 'View person', + 'edit' => 'Edit person', + 'delete' => 'Delete person', + 'messages' => [ + 'createSuccess' => 'Person has been successfully created!', + 'editSuccess' => 'Person has been successfully updated!', + 'deleteSuccess' => 'Person has been removed!', + ], + 'form' => [ + 'avatar' => 'Avatar', + 'avatar_size_hint' => + 'Avatar must be squared and at least 400px wide and tall.', + 'full_name' => 'Full name', + 'full_name_hint' => 'This is the full name or alias of the person.', + 'unique_name' => 'Unique name', + 'unique_name_hint' => 'Used for URLs', + 'information_url' => 'Information URL', + 'information_url_hint' => + 'Url to a relevant resource of information about the person, such as a homepage or third-party profile platform.', + 'submit_create' => 'Create person', + 'submit_edit' => 'Save person', + ], + 'podcast_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this podcast', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'episode_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this episode', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'credits' => 'Credits', +]; diff --git a/modules/Admin/Language/fr-ca/Platforms.php b/modules/Admin/Language/fr-ca/Platforms.php new file mode 100644 index 00000000..e161181c --- /dev/null +++ b/modules/Admin/Language/fr-ca/Platforms.php @@ -0,0 +1,43 @@ + [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', + 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', + 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', + 'visible' => 'Display in podcast homepage?', + 'on_embed' => 'Display on embeddable player?', + 'remove' => 'Remove {platformName}', + 'submit' => 'Save', + 'messages' => [ + 'updateSuccess' => 'Platform links have been successfully updated!', + 'removeLinkSuccess' => 'The platform link has been removed.', + 'removeLinkError' => + 'The platform link could not be removed. Try again.', + ], + 'description' => [ + 'podcasting' => 'The podcast ID on this platform', + 'social' => 'The podcast account ID on this platform', + 'funding' => 'Call to action message', + ], +]; diff --git a/modules/Admin/Language/fr-ca/Podcast.php b/modules/Admin/Language/fr-ca/Podcast.php new file mode 100644 index 00000000..ff0daebc --- /dev/null +++ b/modules/Admin/Language/fr-ca/Podcast.php @@ -0,0 +1,330 @@ + 'All podcasts', + 'no_podcast' => 'No podcast found!', + 'create' => 'Create podcast', + 'import' => 'Import podcast', + 'all_imports' => 'Podcast imports', + 'new_episode' => 'New Episode', + 'view' => 'View podcast', + 'edit' => 'Edit podcast', + 'publish' => 'Publish podcast', + 'publish_edit' => 'Edit publication', + 'delete' => 'Delete podcast', + 'see_episodes' => 'See episodes', + 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', + 'go_to_page' => 'Go to page', + 'latest_episodes' => 'Latest episodes', + 'see_all_episodes' => 'See all episodes', + 'draft' => 'Draft', + 'messages' => [ + 'createSuccess' => 'Podcast successfully created!', + 'editSuccess' => 'Podcast has been successfully updated!', + 'importSuccess' => 'Podcast has been successfully imported!', + 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', + 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, + cover {cover} + banner {banner} + other {media} + }.', + 'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.', + 'podcastFeedUpdateSuccess' => 'Successful update: {number_of_new_episodes, plural, + one {# episode was} + other {# episodes were} + } added to the podcast!', + 'podcastFeedUpToDate' => 'Podcast is already up to date.', + 'publishError' => 'This podcast is either already published or scheduled for publication.', + 'publishEditError' => 'This podcast is not scheduled for publication.', + 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', + 'scheduleDateError' => 'Schedule date must be set!', + ], + 'form' => [ + 'identity_section_title' => 'Podcast identity', + 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + + 'cover' => 'Podcast cover', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'banner' => 'Podcast banner', + 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', + 'banner_delete' => 'Delete podcast banner', + 'title' => 'Title', + 'handle' => 'Handle', + 'handle_hint' => + 'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.', + 'type' => [ + 'label' => 'Type', + 'episodic' => 'Episodic', + 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', + 'serial' => 'Serial', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + ], + 'description' => 'Description', + 'classification_section_title' => 'Classification', + 'classification_section_subtitle' => + 'These fields will impact your audience and competition.', + 'language' => 'Language', + 'category' => 'Category', + 'category_placeholder' => 'Select a category…', + 'other_categories' => 'Other categories', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does it contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'author_section_title' => 'Author', + 'author_section_subtitle' => 'Who is managing the podcast?', + 'owner_name' => 'Owner name', + 'owner_name_hint' => + 'For administrative use only. Visible in the public RSS feed.', + 'owner_email' => 'Owner email', + 'owner_email_hint' => + 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', + 'publisher' => 'Publisher', + 'publisher_hint' => + 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', + 'copyright' => 'Copyright', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this podcast about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real place or fictional', + 'monetization_section_title' => 'Monetization', + 'monetization_section_subtitle' => + 'Earn money thanks to your audience.', + 'premium' => 'Premium', + 'premium_by_default' => 'Episodes must be set as premium by default', + 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', + 'payment_pointer' => 'Payment Pointer for Web Monetization', + 'payment_pointer_hint' => + 'This is your where you will receive money thanks to Web Monetization', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the podcast', + 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', + 'new_feed_url' => 'New feed URL', + 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', + 'old_feed_url' => 'Old feed URL', + 'partnership' => 'Partnership', + 'partner_id' => 'ID', + 'partner_link_url' => 'Link URL', + 'partner_image_url' => 'Image URL', + 'partner_id_hint' => 'Your own partner ID', + 'partner_link_url_hint' => 'The generic partner link address', + 'partner_image_url_hint' => 'The generic partner image address', + 'block' => 'Podcast should be hidden from public catalogues', + 'block_hint' => + 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'complete' => 'Podcast will not be having new episodes', + 'lock' => 'Prevent podcast from being copied', + 'lock_hint' => + 'The purpose is to tell other podcast platforms whether they are allowed to import this feed. A value of yes means that any attempt to import this feed into a new platform should be rejected.', + 'submit_create' => 'Create podcast', + 'submit_edit' => 'Save podcast', + ], + 'category_options' => [ + 'uncategorized' => 'uncategorized', + 'arts' => 'Arts', + 'business' => 'Business', + 'comedy' => 'Comedy', + 'education' => 'Education', + 'fiction' => 'Fiction', + 'government' => 'Government', + 'health_and_fitness' => 'Health & Fitness', + 'history' => 'History', + 'kids_and_family' => 'Kids & Family', + 'leisure' => 'Leisure', + 'music' => 'Music', + 'news' => 'News', + 'religion_and_spirituality' => 'Religion & Spirituality', + 'science' => 'Science', + 'society_and_culture' => 'Society & Culture', + 'sports' => 'Sports', + 'technology' => 'Technology', + 'true_crime' => 'True Crime', + 'tv_and_film' => 'TV & Film', + 'books' => 'Books', + 'design' => 'Design', + 'fashion_and_beauty' => 'Fashion & Beauty', + 'food' => 'Food', + 'performing_arts' => 'Performing Arts', + 'visual_arts' => 'Visual Arts', + 'careers' => 'Careers', + 'entrepreneurship' => 'Entrepreneurship', + 'investing' => 'Investing', + 'management' => 'Management', + 'marketing' => 'Marketing', + 'non_profit' => 'Non-Profit', + 'comedy_interviews' => 'Comedy Interviews', + 'improv' => 'Improv', + 'stand_up' => 'Stand-Up', + 'courses' => 'Courses', + 'how_to' => 'How To', + 'language_learning' => 'Language Learning', + 'self_improvement' => 'Self-Improvement', + 'comedy_fiction' => 'Comedy Fiction', + 'drama' => 'Drama', + 'science_fiction' => 'Science Fiction', + 'alternative_health' => 'Alternative Health', + 'fitness' => 'Fitness', + 'medicine' => 'Medicine', + 'mental_health' => 'Mental Health', + 'nutrition' => 'Nutrition', + 'sexuality' => 'Sexuality', + 'education_for_kids' => 'Education for Kids', + 'parenting' => 'Parenting', + 'pets_and_animals' => 'Pets & Animals', + 'stories_for_kids' => 'Stories for Kids', + 'animation_and_manga' => 'Animation & Manga', + 'automotive' => 'Automotive', + 'aviation' => 'Aviation', + 'crafts' => 'Crafts', + 'games' => 'Games', + 'hobbies' => 'Hobbies', + 'home_and_garden' => 'Home & Garden', + 'video_games' => 'Video Games', + 'music_commentary' => 'Music Commentary', + 'music_history' => 'Music History', + 'music_interviews' => 'Music Interviews', + 'business_news' => 'Business News', + 'daily_news' => 'Daily News', + 'entertainment_news' => 'Entertainment News', + 'news_commentary' => 'News Commentary', + 'politics' => 'Politics', + 'sports_news' => 'Sports News', + 'tech_news' => 'Tech News', + 'buddhism' => 'Buddhism', + 'christianity' => 'Christianity', + 'hinduism' => 'Hinduism', + 'islam' => 'Islam', + 'judaism' => 'Judaism', + 'religion' => 'Religion', + 'spirituality' => 'Spirituality', + 'astronomy' => 'Astronomy', + 'chemistry' => 'Chemistry', + 'earth_sciences' => 'Earth Sciences', + 'life_sciences' => 'Life Sciences', + 'mathematics' => 'Mathematics', + 'natural_sciences' => 'Natural Sciences', + 'nature' => 'Nature', + 'physics' => 'Physics', + 'social_sciences' => 'Social Sciences', + 'documentary' => 'Documentary', + 'personal_journals' => 'Personal Journals', + 'philosophy' => 'Philosophy', + 'places_and_travel' => 'Places & Travel', + 'relationships' => 'Relationships', + 'baseball' => 'Baseball', + 'basketball' => 'Basketball', + 'cricket' => 'Cricket', + 'fantasy_sports' => 'Fantasy Sports', + 'football' => 'Football', + 'golf' => 'Golf', + 'hockey' => 'Hockey', + 'rugby' => 'Rugby', + 'running' => 'Running', + 'soccer' => 'Soccer', + 'swimming' => 'Swimming', + 'tennis' => 'Tennis', + 'volleyball' => 'Volleyball', + 'wilderness' => 'Wilderness', + 'wrestling' => 'Wrestling', + 'after_shows' => 'After Shows', + 'film_history' => 'Film History', + 'film_interviews' => 'Film Interviews', + 'film_reviews' => 'Film Reviews', + 'tv_reviews' => 'TV Reviews', + ], + 'publish_form' => [ + 'back_to_podcast_dashboard' => 'Back to podcast dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'submit' => 'Publish', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', + 'message_warning_submit' => 'Publish anyway', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'not_published' => 'This podcast is not yet published.', + 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", + 'understand' => 'I understand, I want the podcast to be permanently deleted', + 'submit' => 'Delete', + ], + 'by' => 'By {publisher}', + 'season' => 'Season {seasonNumber}', + 'list_of_episodes_year' => '{year} episodes ({episodeCount})', + 'list_of_episodes_season' => + 'Season {seasonNumber} episodes ({episodeCount})', + 'no_episode' => 'No episode found!', + 'follow' => 'Follow', + 'followers' => '{numberOfFollowers, plural, + one {# follower} + other {# followers} + }', + 'posts' => '{numberOfPosts, plural, + one {# post} + other {# posts} + }', + 'activity' => 'Activity', + 'episodes' => 'Episodes', + 'sponsor' => 'Sponsor', + 'funding_links' => 'Funding links for {podcastTitle}', + 'find_on' => 'Find {podcastTitle} on', + 'listen_on' => 'Listen on', +]; diff --git a/modules/Admin/Language/fr-ca/PodcastNavigation.php b/modules/Admin/Language/fr-ca/PodcastNavigation.php new file mode 100644 index 00000000..bb777707 --- /dev/null +++ b/modules/Admin/Language/fr-ca/PodcastNavigation.php @@ -0,0 +1,42 @@ + 'Go to podcast page', + 'rss_feed' => 'RSS feed', + 'dashboard' => 'Podcast dashboard', + 'podcast-view' => 'Home', + 'podcast-edit' => 'Edit podcast', + 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', + 'episodes' => 'Episodes', + 'episode-list' => 'All episodes', + 'episode-create' => 'New episode', + 'analytics' => 'Analytics', + 'podcast-analytics' => 'Audience overview', + 'podcast-analytics-webpages' => 'Web pages visits', + 'podcast-analytics-locations' => 'Locations', + 'podcast-analytics-unique-listeners' => 'Unique listeners', + 'podcast-analytics-players' => 'Players', + 'podcast-analytics-listening-time' => 'Listening time', + 'podcast-analytics-time-periods' => 'Time periods', + 'monetization' => 'Monetization', + 'subscription-list' => 'All subscriptions', + 'subscription-create' => 'Add subscription', + 'contributors' => 'Contributors', + 'contributor-list' => 'All contributors', + 'contributor-add' => 'Add contributor', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', + 'platforms-social' => 'Social networks', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', +]; diff --git a/modules/Admin/Language/fr-ca/Settings.php b/modules/Admin/Language/fr-ca/Settings.php new file mode 100644 index 00000000..4a70dcba --- /dev/null +++ b/modules/Admin/Language/fr-ca/Settings.php @@ -0,0 +1,58 @@ + 'General settings', + 'instance' => [ + 'title' => 'Instance', + 'site_icon' => 'Site icon', + 'site_icon_delete' => 'Delete site icon', + 'site_icon_hint' => 'Site icons are what you see on your browser tabs, bookmarks bar, and when you add a website as a shortcut on mobile devices.', + 'site_icon_helper' => 'Icon must be squared and at least 512px wide and tall.', + 'site_name' => 'Site name', + 'site_description' => 'Site description', + 'submit' => 'Save', + 'editSuccess' => 'Instance has been updated successfully!', + 'deleteIconSuccess' => 'Site icon has been remove successfully!', + ], + 'images' => [ + 'title' => 'Images', + 'subtitle' => 'Here you can regenerate all images based on the originals that were uploaded. To be used if you find that some images are missing. This task may take a while.', + 'regenerate' => 'Regenerate images', + 'regenerationSuccess' => 'All images have been regenerated successfully!', + ], + 'housekeeping' => [ + 'title' => 'Housekeeping', + 'subtitle' => 'Runs various housekeeping tasks. Use this feature if you ever encounter issues with media files or data integrity. These tasks may take a while.', + 'reset_counts' => 'Reset counts', + 'reset_counts_helper' => 'This option will recalculate and reset all data counts (number of followers, posts, comments, …).', + 'rewrite_media' => 'Rewrite media metadata', + 'rewrite_media_helper' => 'This option will delete all superfluous media files and recreate them (images, audio files, transcripts, chapters, …)', + 'rename_episodes_files' => 'Rename episode audio files', + 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', + 'clear_cache' => 'Clear all cache', + 'clear_cache_helper' => 'This option will flush redis cache or writable/cache files.', + 'run' => 'Run housekeeping', + 'runSuccess' => 'Housekeeping has been run successfully!', + ], + 'theme' => [ + 'title' => 'Theme', + 'accent_section_title' => 'Accent color', + 'accent_section_subtitle' => 'Choose the color to determine the look and feel of all public pages.', + 'pine' => 'Pine', + 'crimson' => 'Crimson', + 'amber' => 'Amber', + 'lake' => 'Lake', + 'jacaranda' => 'Jacaranda', + 'onyx' => 'Onyx', + 'submit' => 'Save', + 'setInstanceThemeSuccess' => 'Theme has been updated successfully!', + ], +]; diff --git a/modules/Admin/Language/fr-ca/Soundbite.php b/modules/Admin/Language/fr-ca/Soundbite.php new file mode 100644 index 00000000..a3f828fe --- /dev/null +++ b/modules/Admin/Language/fr-ca/Soundbite.php @@ -0,0 +1,31 @@ + [ + 'title' => 'Soundbites', + 'soundbite' => 'Soundbite', + ], + 'messages' => [ + 'createSuccess' => 'Soundbite has been successfully created!', + 'deleteSuccess' => 'Soundbite has been successfully removed!', + ], + 'form' => [ + 'title' => 'New soundbite', + 'soundbite_title' => 'Soundbite title', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'submit' => 'Create soundbite', + ], + 'play' => 'Play soundbite', + 'stop' => 'Stop soundbite', + 'create' => 'New soundbite', + 'delete' => 'Delete soundbite', +]; diff --git a/modules/Admin/Language/fr-ca/Validation.php b/modules/Admin/Language/fr-ca/Validation.php new file mode 100644 index 00000000..f76c3163 --- /dev/null +++ b/modules/Admin/Language/fr-ca/Validation.php @@ -0,0 +1,17 @@ + + '{field} is either not an image, or it is not wide or tall enough.', + 'is_image_ratio' => + '{field} is either not an image or not of the right ratio.', + 'is_json' => '{field} contains invalid JSON.', +]; diff --git a/modules/Admin/Language/fr-ca/VideoClip.php b/modules/Admin/Language/fr-ca/VideoClip.php new file mode 100644 index 00000000..638de697 --- /dev/null +++ b/modules/Admin/Language/fr-ca/VideoClip.php @@ -0,0 +1,72 @@ + [ + 'title' => 'Video clips', + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Clip is waiting to be processed.', + 'pending' => 'pending', + 'pending_hint' => 'Clip will be generated shortly.', + 'running' => 'running', + 'running_hint' => 'Clip is being generated.', + 'failed' => 'failed', + 'failed_hint' => 'Clip could not be generated: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Clip was generated successfully!', + ], + 'clip' => 'Clip', + 'duration' => 'Job duration', + ], + 'title' => 'Video clip: {videoClipLabel}', + 'download_clip' => 'Download clip', + 'create' => 'New video clip', + 'go_to_page' => 'Go to clip page', + 'retry' => 'Retry clip generation', + 'delete' => 'Delete clip', + 'logs' => 'Job logs', + 'messages' => [ + 'alreadyExistingError' => 'The video clip you are trying to create already exists!', + 'addToQueueSuccess' => 'Video clip has been added to queue, awaiting to be created!', + 'deleteSuccess' => 'Video clip has been successfully removed!', + ], + 'format' => [ + 'landscape' => 'Landscape', + 'portrait' => 'Portrait', + 'squared' => 'Squared', + ], + 'form' => [ + 'title' => 'New video clip', + 'params_section_title' => 'Video clip parameters', + 'clip_title' => 'Clip title', + 'format' => [ + 'label' => 'Choose a format', + 'landscape_hint' => 'With a 16:9 ratio, landscape videos are great for PeerTube, Youtube and Vimeo.', + 'portrait_hint' => 'With a 9:16 ratio, portrait videos are great for TikTok, Youtube shorts and Instagram stories.', + 'squared_hint' => 'With a 1:1 ratio, squared videos are great for Mastodon, Facebook, Twitter and LinkedIn.', + ], + 'theme' => 'Select a theme', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'trim_start' => 'Trim start', + 'trim_end' => 'Trim end', + 'submit' => 'Create video clip', + ], + 'requirements' => [ + 'title' => 'Missing requirements', + 'missing' => 'You have missing requirements. Make sure to add all the required items to be allowed creating a video for this episode!', + 'ffmpeg' => 'FFmpeg', + 'gd' => 'Graphics Draw (GD)', + 'freetype' => 'Freetype library for GD', + 'transcript' => 'Transcript file (.srt)', + ], +]; diff --git a/modules/Admin/Language/fr/AboutCastopod.php b/modules/Admin/Language/fr/AboutCastopod.php new file mode 100644 index 00000000..7cd9412b --- /dev/null +++ b/modules/Admin/Language/fr/AboutCastopod.php @@ -0,0 +1,22 @@ + 'À propos de Castopod', + 'host_name' => 'Nom d’hôte', + 'version' => 'Version de Castopod', + 'php_version' => 'Version de PHP', + 'os' => 'Système d\'exploitation', + 'languages' => 'Langues', + 'update_database' => 'Mettre à jour la base de données', + 'messages' => [ + 'databaseUpdateSuccess' => 'La base de données est à jour!', + ], +]; diff --git a/modules/Admin/Language/fr/Breadcrumb.php b/modules/Admin/Language/fr/Breadcrumb.php index 375caa16..a4ac57a2 100644 --- a/modules/Admin/Language/fr/Breadcrumb.php +++ b/modules/Admin/Language/fr/Breadcrumb.php @@ -14,29 +14,34 @@ return [ ->gateway => 'Accueil', 'podcasts' => 'podcasts', 'episodes' => 'épisodes', - 'subscriptions' => 'subscriptions', + 'subscriptions' => 'abonnements', 'contributors' => 'contributeurs', 'pages' => 'pages', 'settings' => 'paramètres', 'theme' => 'thème', + 'about' => 'à propos', 'add' => 'ajouter', 'new' => 'créer', 'edit' => 'modifier', 'persons' => 'intervenants', 'publish' => 'publier', 'publish-edit' => 'modifier la publication', - 'publish-date-edit' => 'edit publication date', + 'publish-date-edit' => 'définir la date de publication', 'unpublish' => 'dépublier', 'delete' => 'supprimer', + 'remove' => 'retirer', 'fediverse' => 'fédiverse', - 'block-lists' => 'listes de blocage', + 'blocked-actors' => 'acteurs bloqués', + 'blocked-domains' => 'domaines bloqués', 'users' => 'utilisateurs', 'my-account' => 'mon compte', 'change-password' => 'changer le mot de passe', - 'import' => 'importer un flux', + 'imports' => 'imports', + 'sync-feeds' => 'synchroniser les flux', 'platforms' => 'plateformes', 'social' => 'réseaux sociaux', 'funding' => 'financement', + 'monetization-other' => 'autres monétisations', 'analytics' => 'mesures d’audience', 'locations' => 'localisations', 'webpages' => 'pages web', @@ -48,5 +53,5 @@ return [ 'video-clips' => 'extraits vidéo', 'embed' => 'lecteur intégré', 'notifications' => 'notifications', - 'suspend' => 'suspend', + 'suspend' => 'suspendre', ]; diff --git a/modules/Admin/Language/fr/Charts.php b/modules/Admin/Language/fr/Charts.php index 43fb6717..0c32f623 100644 --- a/modules/Admin/Language/fr/Charts.php +++ b/modules/Admin/Language/fr/Charts.php @@ -37,4 +37,5 @@ return [ 'podcast_by_bandwidth' => 'Bande passante quotidienne consommée (en Mo)', 'total_storage_by_month' => 'Stockage mensuel (en Mo)', 'total_bandwidth_by_month' => 'Bande passante mensuelle utilisée (en Mo)', + 'total_bandwidth_by_month_limit' => 'Limité à {totalBandwidth} par mois', ]; diff --git a/modules/Admin/Language/fr/Common.php b/modules/Admin/Language/fr/Common.php index f66f2a29..d53347fb 100644 --- a/modules/Admin/Language/fr/Common.php +++ b/modules/Admin/Language/fr/Common.php @@ -40,12 +40,13 @@ return [ ], 'upload_file' => 'Téléversez un fichier', 'remote_url' => 'URL distante', + 'save' => 'Sauvegarder', ], 'play_episode_button' => [ 'play' => 'Lire', 'playing' => 'En cours', ], 'size_limit' => 'Taille maximale : {0}.', - 'choose_interact' => 'Choose how to interact', - 'view' => 'View', + 'choose_interact' => 'Choisissez comment interagir', + 'view' => 'Voir', ]; diff --git a/modules/Admin/Language/fr/Dashboard.php b/modules/Admin/Language/fr/Dashboard.php index 881073fd..010761fa 100644 --- a/modules/Admin/Language/fr/Dashboard.php +++ b/modules/Admin/Language/fr/Dashboard.php @@ -9,20 +9,20 @@ declare(strict_types=1); */ return [ - 'home' => 'Admin dashboard', - 'welcome_message' => 'Welcome to the admin area!', + 'home' => 'Tableau de bord', + 'welcome_message' => 'Bienvenue dans l\'espace d\'administration !', 'podcasts' => [ 'title' => 'Podcasts', - 'not_found' => 'No published podcast', - 'last_published' => 'Last published on {lastPublicationDate}', + 'not_found' => 'Pas de podcast publié', + 'last_published' => 'Dernière publication le {lastPublicationDate}', ], 'episodes' => [ - 'title' => 'Episodes', - 'not_found' => 'No published episode', - 'last_published' => 'Last published on {lastPublicationDate}', + 'title' => 'Épisodes', + 'not_found' => 'Aucun épisode publié', + 'last_published' => 'Dernière publication le {lastPublicationDate}', ], 'storage' => [ - 'title' => 'Storage', - 'subtitle' => '{totalUploaded} out of {totalStorage}', + 'title' => 'Espace de stockage', + 'subtitle' => '{totalUploaded} sur {totalStorage}', ], ]; diff --git a/modules/Admin/Language/fr/Episode.php b/modules/Admin/Language/fr/Episode.php index dbdf875c..5b479992 100644 --- a/modules/Admin/Language/fr/Episode.php +++ b/modules/Admin/Language/fr/Episode.php @@ -22,16 +22,17 @@ return [ 'all_podcast_episodes' => 'Tous les épisodes du podcast', 'back_to_podcast' => 'Revenir au podcast', 'edit' => 'Modifier', + 'preview' => 'Prévisualisation', 'publish' => 'Publier', 'publish_edit' => 'Modifier la publication', - 'publish_date_edit' => 'Edit publication date', + 'publish_date_edit' => 'Définir la date de publication', 'unpublish' => 'Dépublier', 'publish_error' => 'L’épisode est déjà publié.', 'publish_edit_error' => 'L’épisode est déjà publié.', 'publish_cancel_error' => 'L’épisode est déjà publié.', - 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', - 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', - 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'publish_date_edit_error' => 'L\'épisode n\'a pas encore été publié, vous ne pouvez pas modifier sa date de publication.', + 'publish_date_edit_future_error' => 'La date de publication de l\'épisode ne peut être définie qu\'à une date antérieure! Si vous souhaitez la replanifier, dépubliez-la d\'abord.', + 'publish_date_edit_success' => 'La date de publication de l\'épisode a été mise à jour avec succès !', 'unpublish_error' => 'L’épisode n’est pas publié.', 'delete' => 'Supprimer', 'go_to_page' => 'Voir', @@ -42,7 +43,7 @@ return [ 'scheduled' => 'Planifié', 'not_published' => 'Non publié', ], - 'with_podcast_hint' => 'To be published at the same time as the podcast', + 'with_podcast_hint' => 'Publier en même temps que le podcast', 'list' => [ 'search' => [ 'placeholder' => 'Rechercher un épisode', @@ -50,11 +51,12 @@ return [ 'submit' => 'Recherche', ], 'number_of_episodes' => '{numberOfEpisodes, plural, - one {# episode} - other {# episodes} + one {# épisode} + other {# épisodes} }', 'episode' => 'Épisode', 'visibility' => 'Visibilité', + 'downloads' => 'Téléchargements', 'comments' => 'Commentaires', 'actions' => 'Actions', ], @@ -62,31 +64,31 @@ return [ 'createSuccess' => 'L’épisode a été créé avec succès !', 'editSuccess' => 'L’épisode a bien été mis à jour !', 'publishSuccess' => '{publication_status, select, - published {Episode successfully published!} - scheduled {Episode publication successfully scheduled!} - with_podcast {This episode will be published at the same time as the podcast.} - other {This episode is not published.} + published {Épisode publié avec succès !} + scheduled {La publication de l\'épisode est planifiée avec succès !} + with_podcast {Cet épisode sera publié en même temps que le podcast.} + other {Cet épisode n\'est pas publié.} }', 'publishCancelSuccess' => 'La publication de l’épisode a bien été annulée !', - 'unpublishBeforeDeleteTip' => 'You must unpublish the episode before deleting it.', - 'scheduleDateError' => 'Schedule date must be set!', - 'deletePublishedEpisodeError' => 'Please unpublish the episode before deleting it.', + 'unpublishBeforeDeleteTip' => 'Vous devez dépublier l\'épisode avant de le supprimer.', + 'scheduleDateError' => 'La date de planification doit être définie !', + 'deletePublishedEpisodeError' => 'Vous devez dépublier l\'épisode avant de le supprimer.', 'deleteSuccess' => 'L\'épisode a bien été supprimé !', - 'deleteError' => 'Failed to delete episode {type, select, - transcript {transcript} - chapters {chapters} - image {cover} + 'deleteError' => 'Impossible de supprimer l\'épisode {type, select, + transcript {transcription} + chapters {chapitres} + image {couverture} audio {audio} - other {media} + other {média} }.', - 'deleteFileError' => 'Failed to delete {type, select, - transcript {transcript} - chapters {chapters} - image {cover} + 'deleteFileError' => 'Impossible de supprimer le fichier {type, select, + transcript {de transcription} + chapters {de chapitres} + image {d’image de couverture} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', - 'sameSlugError' => 'An episode with the chosen slug already exists.', + } {file_key}. Vous pouvez le supprimer manuellement de votre disque.', + 'sameSlugError' => 'Il existe déjà un épisode avec le slug choisi.', ], 'form' => [ 'file_size_error' => @@ -97,7 +99,7 @@ return [ 'cover' => 'Image de couverture', 'cover_hint' => 'Si vous ne définissez pas d’image, celle du podcast sera utilisée à la place.', - 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'cover_size_hint' => 'L’image doit être carrée et avoir au moins 1400px de largeur et de hauteur.', 'title' => 'Titre', 'title_hint' => 'Doit contenir un titre d’épisode clair et concis. Ne précisez ici aucun numéro de saison ou d’épisode.', @@ -114,7 +116,7 @@ return [ 'bonus_hint' => 'Contenu supplémentaire pour le podcast (par exemple des informations sur les coulisses ou des interviews avec les acteurs) ou du contenu promotionnel croisé pour un autre podcast', ], 'premium_title' => 'Premium', - 'premium' => 'Episode must be accessible to premium subscribers only', + 'premium' => 'L\'épisode doit être accessible aux abonnés premium uniquement', 'parental_advisory' => [ 'label' => 'Avertissement parental', 'hint' => 'L’épisode contient-il un contenu explicite ?', @@ -131,15 +133,15 @@ return [ 'Ce texte est ajouté à la fin de chaque description d’épisode, c’est un bon endroit pour placer vos liens sociaux par exemple.', 'additional_files_section_title' => 'Fichiers additionels', 'additional_files_section_subtitle' => - 'These files may be used by other platforms to provide better experience to your audience. See the {podcastNamespaceLink} for more information.', + 'Ces fichiers pourront être utilisées par d’autres plate-formes pour procurer une meilleure expérience à vos auditeurs. Consulter le {podcastNamespaceLink} pour plus d’informations.', 'location_section_title' => 'Localisation', 'location_section_subtitle' => 'De quel lieu cet épisode parle-t-il ?', 'location_name' => 'Nom ou adresse du lieu', 'location_name_hint' => 'Ce lieu peut être réel ou fictif', 'transcript' => 'Transcription (sous-titrage)', - 'transcript_hint' => 'Seulement les .srt sont autorisés', + 'transcript_hint' => 'Seuls les .srt ou .vtt sont autorisés.', 'transcript_download' => 'Télécharger le transcript', - 'transcript_file' => 'Fichier de transcription (.srt)', + 'transcript_file' => 'Fichier de transcription (.srt ou .vtt)', 'transcript_remote_url' => 'URL distante pour le fichier de transcription', 'transcript_file_delete' => 'Supprimer le fichier de transcription', 'chapters' => 'Chapitrage', @@ -153,9 +155,9 @@ return [ 'Si vous avez besoin d’une balise que Castopod ne couvre pas, définissez-la ici.', 'custom_rss' => 'Balises RSS personnalisées pour l’épisode', 'custom_rss_hint' => 'Ceci sera injecté dans la balise ❬item❭.', - 'block' => 'Episode should be hidden from public catalogues', + 'block' => 'L\'épisode doit être masqué dans les catalogues publics', 'block_hint' => - 'The episode show or hide status: toggling this on prevents the episode from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'L\'épisode montre ou masque le statut: activer ceci empêche l\'épisode d\'apparaître dans les Podcasts Apple, Google Podcasts, et toutes les applications tierces qui tirent vers ces répertoires. (non garanti)', 'submit_create' => 'Créer l’épisode', 'submit_edit' => 'Enregistrer l’épisode', ], @@ -169,7 +171,7 @@ return [ 'publication_method' => [ 'now' => 'Maintenant', 'schedule' => 'Planifier', - 'with_podcast' => 'Publish alongside podcast', + 'with_podcast' => 'Publier à côté du podcast', ], 'scheduled_publication_date' => 'Date de publication programmée', 'scheduled_publication_date_clear' => 'Effacer la date de publication', @@ -183,19 +185,19 @@ return [ 'message_warning_submit' => 'Publier quand même', ], 'publish_date_edit_form' => [ - 'new_publication_date' => 'New publication date', - 'new_publication_date_hint' => 'Must be set to a past date.', - 'submit' => 'Edit publication date', + 'new_publication_date' => 'Date de la prochaine publication', + 'new_publication_date_hint' => 'Doit être défini à une date antérieure.', + 'submit' => 'Définir la date de publication', ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", + "Dépublier l'épisode supprimera tous les commentaires et messages qui lui sont associés et le supprimera du flux RSS du podcast.", 'understand' => 'Je comprends, je veux dépublier l’épisode', 'submit' => 'Dépublier', ], 'delete_form' => [ 'disclaimer' => - "Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.", + "La suppression de l'épisode supprimera tous les fichiers multimédia, les commentaires, les clips vidéo et les parties sonores qui y sont associées.", 'understand' => 'Je comprends, je veux supprimer l’épisode', 'submit' => 'Supprimer', ], @@ -210,4 +212,14 @@ return [ 'light' => 'Clair', 'light-transparent' => 'Clair transparent', ], + 'publication_status_banner' => [ + 'draft_mode' => 'mode brouillon', + 'text' => '{publication_status, select, + published {Cet épisode n’est pas encore publié.} + scheduled {Cet épisode est programmé pour le {publication_date}.} + with_podcast {Cet épisode va être publié au même moment que le podcast.} + other {Cet épisode n’est pas encore publié.} + }', + 'preview' => 'Prévisualisation', + ], ]; diff --git a/modules/Admin/Language/fr/Navigation.php b/modules/Admin/Language/fr/Navigation.php index 8e870a14..b3fd182b 100644 --- a/modules/Admin/Language/fr/Navigation.php +++ b/modules/Admin/Language/fr/Navigation.php @@ -12,12 +12,14 @@ return [ 'toggle_sidebar' => 'Afficher ou cacher la barre latérale', 'go_to_website' => 'Aller au site', 'go_to_admin' => 'Aller à l’admin', + 'not-authorized' => 'Non autorisé', 'dashboard' => 'Tableau de bord', 'admin' => 'Accueil', 'podcasts' => 'Podcasts', 'podcast-list' => 'Tous les podcasts', 'podcast-create' => 'Créer un podcast', - 'podcast-import' => 'Importer un podcast', + 'all-podcast-imports' => 'Tous les imports de Podcast', + 'podcast-imports-add' => 'Importer un podcast', 'persons' => 'Intervenants', 'person-list' => 'Tous les intervenants', 'person-create' => 'Créer un intervenant', @@ -33,6 +35,7 @@ return [ 'settings' => 'Paramètres', 'settings-general' => 'Général', 'settings-theme' => 'Thème', + 'admin-about' => 'À propos', 'account' => [ 'my-account' => 'Mon compte', 'change-password' => 'Modifier le mot de passe', diff --git a/modules/Admin/Language/fr/Notifications.php b/modules/Admin/Language/fr/Notifications.php index 42f1149b..003b2bbb 100644 --- a/modules/Admin/Language/fr/Notifications.php +++ b/modules/Admin/Language/fr/Notifications.php @@ -10,10 +10,10 @@ declare(strict_types=1); return [ 'title' => 'Notifications', - 'reply' => '{actor_username} replied to your post', - 'favourite' => '{actor_username} favourited your post', - 'reblog' => '{actor_username} shared your post', - 'follow' => '{actor_username} started following you', + 'reply' => '{actor_username} a répondu à votre message', + 'favourite' => '{actor_username} a mis en favori votre message', + 'reblog' => '{actor_username} à partagé votre message', + 'follow' => '{actor_username} commencé à te suivre', 'no_notifications' => 'Aucune notification', 'mark_all_as_read' => 'Tout marquer comme lu', ]; diff --git a/modules/Admin/Language/fr/Person.php b/modules/Admin/Language/fr/Person.php index e5f3128a..afd6d28f 100644 --- a/modules/Admin/Language/fr/Person.php +++ b/modules/Admin/Language/fr/Person.php @@ -24,7 +24,7 @@ return [ 'form' => [ 'avatar' => 'Avatar', 'avatar_size_hint' => - 'Avatar must be squared and at least 400px wide and tall.', + 'L\'avatar doit être carré et au moins 400px de largeur et de hauteur.', 'full_name' => 'Nom complet', 'full_name_hint' => 'Le nom complet ou le pseudonyme de l’intervenant', 'unique_name' => 'Nom unique', diff --git a/modules/Admin/Language/fr/Platforms.php b/modules/Admin/Language/fr/Platforms.php index e6605532..3e2f9eee 100644 --- a/modules/Admin/Language/fr/Platforms.php +++ b/modules/Admin/Language/fr/Platforms.php @@ -9,9 +9,22 @@ declare(strict_types=1); */ return [ - 'title' => 'Plateformes', + 'title' => [ + 'podcasting' => 'Plateformes de Podcasting', + 'social' => 'Réseaux sociaux', + 'funding' => 'Liens de financement', + ], + 'website' => 'Site web', 'home_url' => 'Aller au site {platformName}', + 'register' => 'S\'inscrire', 'submit_url' => 'Soumettez votre podcast sur {platformName}', + 'your_link' => 'Votre lien', + 'your_id' => [ + 'podcasting' => 'Votre identifiant', + 'social' => 'Votre identifiant', + 'funding' => 'Votre CTA', + ], + 'your_cta' => 'Votre appel à l\'action', 'visible' => 'Afficher sur la page d’accueil du podcast ?', 'on_embed' => 'Afficher sur le lecteur intégré ?', 'remove' => 'Supprimer {platformName}', diff --git a/modules/Admin/Language/fr/Podcast.php b/modules/Admin/Language/fr/Podcast.php index 41255ff2..feadf6d3 100644 --- a/modules/Admin/Language/fr/Podcast.php +++ b/modules/Admin/Language/fr/Podcast.php @@ -13,54 +13,57 @@ return [ 'no_podcast' => 'Aucun podcast trouvé !', 'create' => 'Créer un podcast', 'import' => 'Importer un podcast', + 'all_imports' => 'Imports de podcasts', 'new_episode' => 'Créer un épisode', 'view' => 'Voir le podcast', 'edit' => 'Modifier le podcast', - 'publish' => 'Publish podcast', - 'publish_edit' => 'Edit publication', + 'publish' => 'Publier le podcast', + 'publish_edit' => 'Modifier la publication', 'delete' => 'Supprimer le podcast', 'see_episodes' => 'Voir les épisodes', 'see_contributors' => 'Voir les contributeurs', + 'monetization_other' => 'Autres monétisations', 'go_to_page' => 'Aller à la page', 'latest_episodes' => 'Derniers épisodes', 'see_all_episodes' => 'Voir tous les épisodes', - 'draft' => 'Draft', + 'draft' => 'Brouillon', 'messages' => [ - 'createSuccess' => 'Podcast successfully created!', + 'createSuccess' => 'Le podcast a été créé avec succès !', 'editSuccess' => 'Le podcast a bien été mis à jour !', 'importSuccess' => 'Le podcast a été importé avec succès !', - 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', - 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, - cover {cover} - banner {banner} - other {media} + 'deleteSuccess' => 'Podcast @{podcast_handle} a été supprimé avec succès !', + 'deletePodcastMediaError' => 'Impossible de supprimer le podcast {type, select, + cover {couverture} + banner {bannière} + other {média} }.', - 'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select, - transcript {transcript} - chapters {chapters} - image {cover} + 'deleteEpisodeMediaError' => 'Impossible de supprimer l\'épisode de podcast {episode_slug} {type, select, + transcript {transcription} + chapters {chapitres} + image {couverture} audio {audio} - other {media} + other {média} }.', - 'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.', - 'podcastFeedUpdateSuccess' => 'Successful update: {number_of_new_episodes, plural, - one {# episode was} - other {# episodes were} - } added to the podcast!', - 'podcastFeedUpToDate' => 'Podcast is already up to date.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', - 'publishError' => 'This podcast is either already published or scheduled for publication.', - 'publishEditError' => 'This podcast is not scheduled for publication.', - 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', - 'scheduleDateError' => 'Schedule date must be set!', + 'deletePodcastMediaFolderError' => 'Impossible de supprimer le dossier de podcast {folder_path}. Vous pouvez le supprimer manuellement de votre disque.', + 'podcastFeedUpdateSuccess' => 'Mise à jour réussie : {number_of_new_episodes, plural, + one {# épisode a été} + other {# épisodes ont été} + } ajoutés au podcast!', + 'podcastFeedUpToDate' => 'Le podcast est déjà à jour.', + 'publishError' => 'Ce podcast est soit déjà publié, soit programmé pour publication.', + 'publishEditError' => 'Ce podcast n\'est pas programmé pour publication.', + 'publishCancelSuccess' => 'Publication de Podcast annulée avec succès !', + 'scheduleDateError' => 'La date de planification doit être définie !', ], 'form' => [ 'identity_section_title' => 'Informations sur le Podcast', 'identity_section_subtitle' => 'Ces champs vous permettent de vous faire remarquer.', + 'fediverse_section_title' => 'Identité dans le Fédivers', + 'cover' => 'Couverture du podcast', - 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'cover_size_hint' => 'La couverture du podcast doit être carrée, avec au minimum 1400px de largeur et de hauteur.', 'banner' => 'Bannière du podcast', - 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', + 'banner_size_hint' => 'La bannière doit être au format 3/1, avec au minimum 1500px de largeur.', 'banner_delete' => 'Supprimer la bannière du podcast', 'title' => 'Titre', 'handle' => 'Identifiant', @@ -71,7 +74,17 @@ return [ 'episodic' => 'Épisodique', 'episodic_hint' => 'Si les épisodes sont destinés à être consommés sans ordre spécifique. Les épisodes les plus récents seront présentés en premier.', 'serial' => 'Série', - 'serial_hint' => 'Si les épisodes sont destinés à être consommés dans un ordre séquentiel bien défini. Les épisodes les plus anciens seront présentés en premier.', + 'serial_hint' => 'Si les épisodes sont destinés à êtres lus dans un ordre séquentiel. Les épisodes seront présentés dans l’ordre de numérotation.', + ], + 'medium' => [ + 'label' => 'Médium', + 'hint' => 'Médium tel que représenté par la balise podcast:medium dans le flux RSS. Changer ceci peut changer la façon dont les lecteurs présentent votre flux.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Décrit un flux pour une émission de podcast.', + 'music' => 'Musique', + 'music_hint' => 'Un flux de musique organisé en un "album", chaque élément étant une chanson de l\'album.', + 'audiobook' => 'Livre audio', + 'audiobook_hint' => 'Types d\'audio spécifiques avec un élément par flux, ou lorsque des éléments représentent des chapitres dans le livre.', ], 'description' => 'Description', 'classification_section_title' => 'Classification', @@ -96,6 +109,8 @@ return [ 'owner_email' => 'E-mail du/de la propriétaire', 'owner_email_hint' => 'Utilisé par la plupart des plateformes pour vérifier la propriété du podcast. Visible dans le flux RSS public.', + 'is_owner_email_removed_from_feed' => 'Retirer l\'email du propriétaire du flux RSS publique', + 'is_owner_email_removed_from_feed_hint' => 'Il se peut que vous deviez temporairement exposer l’email pour qu\'un index puisse valider la propriété de votre podcast.', 'publisher' => 'Éditeur / Éditrice', 'publisher_hint' => 'Le groupe responsable de la création du podcast. Fait souvent référence à la société mère ou au réseau d’un podcast. Ce champ est parfois appelé « Auteur ».', @@ -108,8 +123,13 @@ return [ 'monetization_section_subtitle' => 'Gagnez de l’argent grâce à votre audience.', 'premium' => 'Premium', - 'premium_by_default' => 'Episodes must be set as premium by default', - 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'premium_by_default' => 'Les épisodes doivent être définis comme premium par défaut', + 'premium_by_default_hint' => 'Les épisodes de Podcast seront marqués comme premium par défaut. Vous pouvez toujours choisir de définir certains épisodes, bandes-annonces ou bonus comme publics.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visitez votre tableau de bord OP3 (lien externe)', + 'op3_hint' => 'Valorisez vos données d\'analyse avec OP3, un service d\'analyse tiers open source et de confiance. Partagez, validez et comparez vos données analytiques avec l\'écosystème de podcasting ouvert.', + 'op3_enable' => 'Activer le service d\'analyse OP3', + 'op3_enable_hint' => 'Pour des raisons de sécurité, les données d\'analyse des épisodes premium ne seront pas partagées avec OP3.', 'payment_pointer' => 'Adresse de paiement (Payment Pointer) pour Web Monetization', 'payment_pointer_hint' => 'L’adresse où vous recevrez de l’argent grâce à Web Monetization', @@ -118,11 +138,12 @@ return [ 'Si vous avez besoin d’une balise que nous n’avons pas couverte, définissez-la ici.', 'custom_rss' => 'Balises RSS personnalisées pour le podcast', 'custom_rss_hint' => 'Ceci sera injecté dans la balise ❬channel❭.', + 'verify_txt' => 'Vérification de propriété TXT', + 'verify_txt_hint' => 'Plutôt que de se fier au courrier électronique, certains services tiers peuvent confirmer la propriété de votre podcast en vous demandant d\'intégrer un texte de vérification dans votre flux.', + 'verify_txt_helper' => 'Ce texte est injecté dans une balise .', 'new_feed_url' => 'URL du nouveau flux', 'new_feed_url_hint' => 'Utilisez ce champ lorsque vous déplacez ce podcast vers un autre domaine ou que vous changez d’hébergeur. Par défaut, ce champ est rempli avec l’URL du flux actuel si le podcast est importé.', - 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', + 'old_feed_url' => 'URL de l\'ancien flux', 'partnership' => 'Partenariat', 'partner_id' => 'ID', 'partner_link_url' => 'URL lien', @@ -130,10 +151,9 @@ return [ 'partner_id_hint' => 'Votre identifiant personnel partenaire', 'partner_link_url_hint' => 'L’adresse générique des liens partenaire', 'partner_image_url_hint' => 'L’adresse générique des images partenaire', - 'status_section_title' => 'Statut', - 'block' => 'Podcast should be hidden from public catalogues', + 'block' => 'L\'épisode doit être masqué dans les catalogues publics', 'block_hint' => - 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'Le statut d\'affichage ou de masquage du podcast : activer cette option empêche l\'intégralité du podcast d\'apparaître dans les podcasts Apple, Google Podcasts et toutes les applications tierces qui extraient des émissions de ces répertoires. (Pas garantie)', 'complete' => 'Le podcast n’aura plus de nouveaux épisodes.', 'lock' => 'Empêcher la copie du podcast', 'lock_hint' => @@ -255,36 +275,36 @@ return [ 'tv_reviews' => 'Critiques TV', ], 'publish_form' => [ - 'back_to_podcast_dashboard' => 'Back to podcast dashboard', - 'post' => 'Your announcement post', + 'back_to_podcast_dashboard' => 'Retour au tableau de bord des podcasts', + 'post' => 'Votre message de publication', 'post_hint' => - "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", - 'message_placeholder' => 'Write your message…', - 'submit' => 'Publish', - 'publication_date' => 'Publication date', + "Écrivez un message pour annoncer la publication de votre podcast. Le message sera affiché sur la page d'accueil de votre podcast.", + 'message_placeholder' => 'Rédiger votre message…', + 'submit' => 'Publier', + 'publication_date' => 'Date de publication', 'publication_method' => [ - 'now' => 'Now', - 'schedule' => 'Schedule', + 'now' => 'Maintenant', + 'schedule' => 'Planifier', ], - 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date' => 'Date de publication programmée', 'scheduled_publication_date_hint' => - 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', - 'submit_edit' => 'Edit publication', - 'cancel_publication' => 'Cancel publication', - 'message_warning' => 'You did not write a message for your announcement post!', - 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', - 'message_warning_submit' => 'Publish anyway', + 'Vous pouvez planifier la sortie de l’épisode en saisissant une date de publication. Ce champ doit être au format YYYY-MM-DD HH:mm', + 'submit_edit' => 'Modifier la publication', + 'cancel_publication' => 'Annuler la publication', + 'message_warning' => 'Vous n’avez pas saisi de message pour l’annonce de votre épisode !', + 'message_warning_hint' => 'Avoir un message augmente l\'engagement social, résultant en une meilleure visibilité pour votre podcast.', + 'message_warning_submit' => 'Publier quand même', ], 'publication_status_banner' => [ - 'draft_mode' => 'draft mode', - 'not_published' => 'This podcast is not yet published.', - 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + 'draft_mode' => 'Mode brouillon', + 'not_published' => 'Ce podcast n\'est pas encore publié.', + 'scheduled' => 'Ce podcast est programmé pour être publié le {publication_date}.', ], 'delete_form' => [ 'disclaimer' => - "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", - 'understand' => 'I understand, I want the podcast to be permanently deleted', - 'submit' => 'Delete', + "La suppression du podcast supprimera tous les épisodes, fichiers multimédia, messages et statistiques qui y sont associées. Cette action est irréversible, vous ne pourrez plus les récupérer par la suite.", + 'understand' => 'Je comprends, je souhaite que le podcast soit définitivement supprimé', + 'submit' => 'Supprimer', ], 'by' => 'Par {publisher}', 'season' => 'Saison {seasonNumber}', @@ -294,12 +314,12 @@ return [ 'no_episode' => 'Aucun épisode trouvé !', 'follow' => 'Suivre', 'followers' => '{numberOfFollowers, plural, - one {# follower} - other {# followers} + one {# abonné·e} + other {# abonné·e·s} }', 'posts' => '{numberOfPosts, plural, - one {# post} - other {# posts} + one {# publication} + other {# publications} }', 'activity' => 'Activité', 'episodes' => 'Épisodes', diff --git a/modules/Admin/Language/fr/PodcastImport.php b/modules/Admin/Language/fr/PodcastImport.php deleted file mode 100644 index b72cf0d2..00000000 --- a/modules/Admin/Language/fr/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'Le podcast à importer', - 'old_podcast_section_subtitle' => - 'Assurez-vous d’être détenteur des droits du podcast avant de l’importer. Copier et diffuser un podcast sans en détenir les droits est assimilable à de la contrefaçon et est passible de poursuites.', - 'imported_feed_url' => 'Adresse du flux', - 'imported_feed_url_hint' => 'Le flux doit être au format xml ou rss.', - 'new_podcast_section_title' => 'Le nouveau podcast', - 'advanced_params_section_title' => 'Paramètres avancés', - 'advanced_params_section_subtitle' => - 'Si vous ne savez pas à quoi servent ces champs, conservez les valeurs par défaut.', - 'slug_field' => 'Champ à utiliser pour calculer l’identifiant de l’épisode', - 'description_field' => - 'Champs pour la description des épisodes', - 'force_renumber' => 'Forcer la re-numérotation des épisodes', - 'force_renumber_hint' => - 'Utilisez ceci si le podcast à importer ne contient pas de numéros d’épisodes mais que vous souhaitez en ajouter pendant l’import.', - 'season_number' => 'Numéro de saison', - 'season_number_hint' => - 'Utilisez ceci si le podcast à importer ne contient pas de numéros de saison mais que vous souhaitez en définir un. Laissez vide sinon.', - 'max_episodes' => 'Nombre maximum d’épisodes à importer', - 'max_episodes_hint' => 'Laissez vide pour importer tous les épisodes', - 'lock_import' => - 'Ce flux est protégé. Vous ne pouvez pas l’importer. Si en vous êtes le propriétaire, déprotégez-le sur la plate-forme d’origine.', - 'submit' => 'Importer le podcast', -]; diff --git a/modules/Admin/Language/fr/PodcastNavigation.php b/modules/Admin/Language/fr/PodcastNavigation.php index 86a1bf52..f02631d4 100644 --- a/modules/Admin/Language/fr/PodcastNavigation.php +++ b/modules/Admin/Language/fr/PodcastNavigation.php @@ -10,10 +10,13 @@ declare(strict_types=1); return [ 'go_to_page' => 'Aller à la page du podcast', + 'rss_feed' => 'Flux RSS', 'dashboard' => 'Tableau de bord du podcast', 'podcast-view' => 'Accueil', 'podcast-edit' => 'Modifier le podcast', 'podcast-persons-manage' => 'Gestion des intervenants', + 'podcast-imports' => 'Imports de podcasts', + 'podcast-imports-sync' => 'Synchronisation des flux', 'episodes' => 'Épisodes', 'episode-list' => 'Tous les épisodes', 'episode-create' => 'Créer un épisode', @@ -25,14 +28,15 @@ return [ 'podcast-analytics-players' => 'Lecteurs', 'podcast-analytics-listening-time' => 'Durée d’écoute', 'podcast-analytics-time-periods' => 'Périodes', - 'premium' => 'Premium', - 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', + 'monetization' => 'Monétisation', + 'subscription-list' => 'Tous les abonnements', + 'subscription-create' => 'Ajouter un abonnement', 'contributors' => 'Contributeurs', 'contributor-list' => 'Tous les contributeurs', 'contributor-add' => 'Ajouter un contributeur', - 'platforms' => 'Plate-formes externes', - 'platforms-podcasting' => 'Podcasts', + 'broadcast' => 'Diffusion', + 'platforms-podcasting' => 'Apps de Podcasting', 'platforms-social' => 'Réseaux sociaux', - 'platforms-funding' => 'Financement', + 'platforms-funding' => 'Liens de financement', + 'podcast-monetization-other' => 'Autre', ]; diff --git a/modules/Admin/Language/fr/Settings.php b/modules/Admin/Language/fr/Settings.php index e6b0f547..f32fc3ec 100644 --- a/modules/Admin/Language/fr/Settings.php +++ b/modules/Admin/Language/fr/Settings.php @@ -35,8 +35,8 @@ return [ 'reset_counts_helper' => 'Cette option recalcule et réinitialise les compteurs de données (nombre d’abonné·e·s, de publications, de commentaires, …).', 'rewrite_media' => 'Réécrire les métadonnées des fichiers média', 'rewrite_media_helper' => 'Cette option supprimera tous les fichiers média superflus et les recréera (images, fichiers audio, transcripts, chapitrages, …)', - 'rename_episodes_files' => 'Rename episode audio files', - 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', + 'rename_episodes_files' => 'Renommer les fichiers audio de l\'épisode', + 'rename_episodes_files_hint' => 'Cette option renommera tous les fichiers audio des épisodes en une chaîne de caractères aléatoire. Utilisez-le si l\'un de vos liens d\'épisodes privés a été divulgué, car cela le masquera efficacement.', 'clear_cache' => 'Supprimer tout le cache', 'clear_cache_helper' => 'Cette option supprimera l’intégralité du cache redis ou des fichiers cache du dossier writable/cache.', 'run' => 'Faire le ménage', diff --git a/modules/Admin/Language/fr/Validation.php b/modules/Admin/Language/fr/Validation.php index e9989330..2054829b 100644 --- a/modules/Admin/Language/fr/Validation.php +++ b/modules/Admin/Language/fr/Validation.php @@ -13,6 +13,5 @@ return [ '{field} n’est pas une image ou n’a pas la taille minimale requise.', 'is_image_ratio' => '{field} n’est pas une image ou n’est pas au bon format.', - 'validate_url' => - 'Le champs {field} doit être une adresse valide (par exemple https://exemple.com/).', + 'is_json' => '{field} contient un JSON non valide.', ]; diff --git a/modules/Admin/Language/fr2/AboutCastopod.php b/modules/Admin/Language/fr2/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/fr2/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/fr2/Breadcrumb.php b/modules/Admin/Language/fr2/Breadcrumb.php new file mode 100644 index 00000000..52e423ee --- /dev/null +++ b/modules/Admin/Language/fr2/Breadcrumb.php @@ -0,0 +1,57 @@ + 'fil d’Ariane', + config('Admin') + ->gateway => 'Accueil', + 'podcasts' => 'podcasts', + 'episodes' => 'épisodes', + 'subscriptions' => 'abonnements', + 'contributors' => 'contributeurs', + 'pages' => 'pages', + 'settings' => 'paramètres', + 'theme' => 'thème', + 'about' => 'à propos', + 'add' => 'ajouter', + 'new' => 'créer', + 'edit' => 'modifier', + 'persons' => 'intervenants', + 'publish' => 'publier', + 'publish-edit' => 'modifier la publication', + 'publish-date-edit' => 'modifier la date de publication', + 'unpublish' => 'dépublier', + 'delete' => 'supprimer', + 'remove' => 'retirer', + 'fediverse' => 'fédiverse', + 'blocked-actors' => 'utilisateurs bloqués', + 'blocked-domains' => 'domaines bloqués', + 'users' => 'utilisateurs', + 'my-account' => 'mon compte', + 'change-password' => 'changer le mot de passe', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', + 'platforms' => 'plateformes', + 'social' => 'réseaux sociaux', + 'funding' => 'financement', + 'monetization-other' => 'other monetization', + 'analytics' => 'mesure d’audience', + 'locations' => 'localisation', + 'webpages' => 'pages web', + 'unique-listeners' => 'auditeurs uniques', + 'players' => 'lecteurs', + 'listening-time' => 'durée d’écoute', + 'time-periods' => 'périodes', + 'soundbites' => 'extraits sonores', + 'video-clips' => 'extraits vidéo', + 'embed' => 'lecteur intégré', + 'notifications' => 'notifications', + 'suspend' => 'suspendre', +]; diff --git a/modules/Admin/Language/fr2/Charts.php b/modules/Admin/Language/fr2/Charts.php new file mode 100644 index 00000000..22759389 --- /dev/null +++ b/modules/Admin/Language/fr2/Charts.php @@ -0,0 +1,41 @@ + 'Téléchargements d’épisodes par service (la semaine dernière)', + 'by_player_weekly' => 'Téléchargements d’épisodes par lecteur (la semaine dernière)', + 'by_player_yearly' => 'Téléchargements d’épisodes par lecteur (cette année)', + 'by_device_weekly' => 'Téléchargements d’épisodes par appareil (la semaine dernière)', + 'by_os_weekly' => 'Téléchargements d’épisodes par OS (la semaine dernière)', + 'podcast_by_region' => 'Téléchargements d’épisodes par région (la semaine dernière)', + 'unique_daily_listeners' => 'Auditeurs uniques quotidiens', + 'unique_monthly_listeners' => 'Auditeurs uniques mensuels', + 'by_browser' => 'Fréquentation des pages web par navigateur (la semaine dernière)', + 'podcast_by_day' => 'Téléchargements quotidiens d’épisodes', + 'podcast_by_month' => 'Téléchargements mensuels d’épisodes', + 'episode_by_day' => 'Téléchargements quotidiens de l’épisode (les 60 premiers jours)', + 'episode_by_month' => 'Téléchargements mensuels de l’épisode', + 'episodes_by_day' => + 'Téléchargements des 5 derniers épisodes (lors de leurs 60 premiers jours)', + 'by_country_weekly' => 'Téléchargements d’épisodes par pays (la dernière semaine)', + 'by_country_yearly' => 'Téléchargements d’épisodes par pays (l\'année dernière)', + 'by_domain_weekly' => 'Fréquentation des pages web par origine (la semaine dernière)', + 'by_domain_yearly' => 'Fréquentation des pages web par origine (la semaine dernière)', + 'by_entry_page' => 'Fréquentation des pages web par page d’entrée (la semaine dernière)', + 'podcast_bots' => 'Robots (bots)', + 'daily_listening_time' => 'Durée quotidienne d’écoute cumulée', + 'monthly_listening_time' => 'Durée mensuelle d’écoute cumulée', + 'by_weekday' => 'Par jour de la semaine (les 60 derniers jours)', + 'by_hour' => 'Par heure de la journée (les 60 derniers jours)', + 'podcast_by_bandwidth' => 'Bande passante quotidienne consommée (en Mo)', + 'total_storage_by_month' => 'Stockage mensuel (en Mo)', + 'total_bandwidth_by_month' => 'Bande passante mensuelle utilisée (en Mo)', + 'total_bandwidth_by_month_limit' => 'Limité à {totalBandwidth} par mois', +]; diff --git a/modules/Admin/Language/fr2/Common.php b/modules/Admin/Language/fr2/Common.php new file mode 100644 index 00000000..73daacf7 --- /dev/null +++ b/modules/Admin/Language/fr2/Common.php @@ -0,0 +1,52 @@ + 'Oui', + 'no' => 'Non', + 'cancel' => 'Annuler', + 'optional' => 'Optionnel', + 'more' => 'Plus', + 'no_data' => 'Aucune donnée trouvée  !', + 'close' => 'Fermer', + 'edit' => 'Modifier', + 'copy' => 'Copier', + 'copied' => 'Copié  !', + 'home' => 'Accueil', + 'explicit' => 'Explicite', + 'powered_by' => 'Propulsé par {castopod}', + 'actions' => 'Actions', + 'pageInfo' => 'Page {currentPage} sur {pageCount}', + 'go_back' => 'Retour en arrière', + 'forms' => [ + 'editor' => [ + 'write' => 'Écrire', + 'preview' => 'Aperçu', + 'help' => 'Propulsé par markdown', + ], + 'multiSelect' => [ + 'selectText' => 'Cliquez pour selectionner', + 'loadingText' => 'Chargement…', + 'noResultsText' => 'Aucun résultat trouvé', + 'noChoicesText' => 'Aucune sélection possible', + 'maxItemText' => 'Impossible de rajouter un élément', + ], + 'upload_file' => 'Téléverser un fichier', + 'remote_url' => 'URL distante', + 'save' => 'Save', + ], + 'play_episode_button' => [ + 'play' => 'Lire', + 'playing' => 'En cours', + ], + 'size_limit' => 'Taille maximale : {0}.', + 'choose_interact' => 'Choisissez le mode d\'interaction', + 'view' => 'Voir', +]; diff --git a/modules/Admin/Language/fr2/Countries.php b/modules/Admin/Language/fr2/Countries.php new file mode 100644 index 00000000..a0e70692 --- /dev/null +++ b/modules/Admin/Language/fr2/Countries.php @@ -0,0 +1,264 @@ + 'Andorre', + 'AE' => 'Émirats Arabes Unis', + 'AF' => 'Afghanistan', + 'AG' => 'Antigua-Et-Barbuda', + 'AI' => 'Anguilla', + 'AL' => 'Albanie', + 'AM' => 'Arménie', + 'AO' => 'Angola', + 'AQ' => 'Antarctique', + 'AR' => 'Argentine', + 'AS' => 'Samoa Américaines', + 'AT' => 'Autriche', + 'AU' => 'Australie', + 'AW' => 'Aruba', + 'AX' => 'Åland, Îles', + 'AZ' => 'Azerbaïdjan', + 'BA' => 'Bosnie-Herzégovine', + 'BB' => 'Barbade', + 'BD' => 'Bangladesh', + 'BE' => 'Belgique', + 'BF' => 'Burkina Faso', + 'BG' => 'Bulgarie', + 'BH' => 'Bahreïn', + 'BI' => 'Burundi', + 'BJ' => 'Bénin', + 'BL' => 'Saint-Barthélemy', + 'BM' => 'Bermudes', + 'BN' => 'Brunéi Darussalam', + 'BO' => 'Bolivie, État Plurinational De', + 'BQ' => 'Bonaire, Saint-Eustache Et Saba', + 'BR' => 'Brésil', + 'BS' => 'Bahamas', + 'BT' => 'Bhoutan', + 'BV' => 'Bouvet, Île', + 'BW' => 'Botswana', + 'BY' => 'Bélarus', + 'BZ' => 'Bélize', + 'CA' => 'Canada', + 'CC' => 'Cocos (Keeling), Îles', + 'CD' => 'Congo, La République Démocratique Du', + 'CF' => 'Centrafricaine, République', + 'CG' => 'Congo', + 'CH' => 'Suisse', + 'CI' => "Côte D’ivoire", + 'CK' => 'Cook, Îles', + 'CL' => 'Chili', + 'CM' => 'Cameroun', + 'CN' => 'Chine', + 'CO' => 'Colombie', + 'CR' => 'Costa Rica', + 'CU' => 'Cuba', + 'CV' => 'Cap-Vert', + 'CW' => 'Curaçao', + 'CX' => 'Christmas, Île', + 'CY' => 'Chypre', + 'CZ' => 'Tchéquie', + 'DE' => 'Allemagne', + 'DJ' => 'Djibouti', + 'DK' => 'Danemark', + 'DM' => 'Dominique', + 'DO' => 'République Dominicaine', + 'DZ' => 'Algérie', + 'EC' => 'Équateur', + 'EE' => 'Estonie', + 'EG' => 'Égypte', + 'EH' => 'Sahara Occidental', + 'ER' => 'Érythrée', + 'ES' => 'Espagne', + 'ET' => 'Éthiopie', + 'FI' => 'Finlande', + 'FJ' => 'Fidji', + 'FK' => 'Falkland, Îles (Malvinas)', + 'FM' => 'Micronésie, États Fédérés De', + 'FO' => 'Féroé, Îles', + 'FR' => 'France', + 'GA' => 'Gabon', + 'GB' => 'Royaume-Uni', + 'GD' => 'Grenade', + 'GE' => 'Géorgie', + 'GF' => 'Guyane Française', + 'GG' => 'Guernesey', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GL' => 'Groenland', + 'GM' => 'Gambie', + 'GN' => 'Guinée', + 'GP' => 'Guadeloupe', + 'GQ' => 'Guinée Équatoriale', + 'GR' => 'Grèce', + 'GS' => 'Géorgie Du Sud Et Les Îles Sandwich Du Sud', + 'GT' => 'Guatémala', + 'GU' => 'Guam', + 'GW' => 'Guinée-Bissau', + 'GY' => 'Guyane', + 'HK' => 'Hong Kong', + 'HM' => 'Heard Et Macdonald, Îles', + 'HN' => 'Honduras', + 'HR' => 'Croatie', + 'HT' => 'Haïti', + 'HU' => 'Hongrie', + 'ID' => 'Indonésie', + 'IE' => 'Irlande', + 'IL' => 'Israël', + 'IM' => 'Île De Man', + 'IN' => 'Inde', + 'IO' => 'Océan Indien, Territoire Britannique De L’', + 'IQ' => 'Irak', + 'IR' => 'Iran, République Islamique D’', + 'IS' => 'Islande', + 'IT' => 'Italie', + 'JE' => 'Jersey', + 'JM' => 'Jamaïque', + 'JO' => 'Jordanie', + 'JP' => 'Japon', + 'KE' => 'Kenya', + 'KG' => 'Kirghizistan', + 'KH' => 'Cambodge', + 'KI' => 'Kiribati', + 'KM' => 'Comores', + 'KN' => 'Saint-Kitts-Et-Nevis', + 'KP' => "Corée, République Populaire Démocratique De", + 'KR' => 'Corée, République De', + 'KW' => 'Koweït', + 'KY' => 'Caïmanes, Îles', + 'KZ' => 'Kazakhstan', + 'LA' => "Lao, République Démocratique Populaire", + 'LB' => 'Liban', + 'LC' => 'Sainte-Lucie', + 'LI' => 'Liechtenstein', + 'LK' => 'Sri Lanka', + 'LR' => 'Libéria', + 'LS' => 'Lesotho', + 'LT' => 'Lituanie', + 'LU' => 'Luxembourg', + 'LV' => 'Lettonie', + 'LY' => 'Libye', + 'MA' => 'Maroc', + 'MC' => 'Monaco', + 'MD' => 'Moldavie', + 'ME' => 'Monténégro', + 'MF' => 'Saint-Martin (Partie Française)', + 'MG' => 'Madagascar', + 'MH' => 'Marshall, Îles', + 'MK' => 'République De Macédoine', + 'ML' => 'Mali', + 'MM' => 'Birmanie', + 'MN' => 'Mongolie', + 'MO' => 'Macao', + 'MP' => 'Mariannes Du Nord, Îles', + 'MQ' => 'Martinique', + 'MR' => 'Mauritanie', + 'MS' => 'Montserrat', + 'MT' => 'Malte', + 'MU' => 'Maurice', + 'MV' => 'Maldives', + 'MW' => 'Malawi', + 'MX' => 'Mexique', + 'MY' => 'Malaisie', + 'MZ' => 'Mozambique', + 'N/A' => 'Non Applicable (IP locale…)', + 'NA' => 'Namibie', + 'NC' => 'Nouvelle-Calédonie', + 'NE' => 'Niger', + 'NF' => 'Norfolk, Île', + 'NG' => 'Nigéria', + 'NI' => 'Nicaragua', + 'NL' => 'Pays-Bas', + 'NO' => 'Norvège', + 'NP' => 'Népal', + 'NR' => 'Nauru', + 'NU' => 'Niué', + 'NZ' => 'Nouvelle-Zélande', + 'OM' => 'Oman', + 'PA' => 'Panama', + 'PE' => 'Pérou', + 'PF' => 'Polynésie Française', + 'PG' => 'Papouasie-Nouvelle-Guinée', + 'PH' => 'Philippines', + 'PK' => 'Pakistan', + 'PL' => 'Pologne', + 'PM' => 'Saint-Pierre-Et-Miquelon', + 'PN' => 'Îles Pitcairn', + 'PR' => 'Porto Rico', + 'PS' => 'État De Palestine', + 'PT' => 'Portugal', + 'PW' => 'Palaos', + 'PY' => 'Paraguay', + 'QA' => 'Qatar', + 'RE' => 'La Réunion', + 'RO' => 'Roumanie', + 'RS' => 'Serbie', + 'RU' => 'Russie, Fédération De', + 'RW' => 'Rwanda', + 'SA' => 'Arabie Saoudite', + 'SB' => 'Salomon, Îles', + 'SC' => 'Seychelles', + 'SD' => 'Soudan', + 'SE' => 'Suède', + 'SG' => 'Singapour', + 'SH' => 'Sainte-Hélène, Ascension Et Tristan Da Cunha', + 'SI' => 'Slovénie', + 'SJ' => 'Svalbard Et Île Jan Mayen', + 'SK' => 'Slovaquie', + 'SL' => 'Sierra Leone', + 'SM' => 'Saint-Marin', + 'SN' => 'Sénégal', + 'SO' => 'Somalie', + 'SR' => 'Suriname', + 'SS' => 'Soudan Du Sud', + 'ST' => 'Sao Tomé-Et-Principe', + 'SV' => 'El Salvador', + 'SX' => 'Saint-Martin (Partie Néerlandaise)', + 'SY' => 'Syrienne, République Arabe', + 'SZ' => 'Eswatini', + 'TC' => 'Turks Et Caïques, Îles', + 'TD' => 'Tchad', + 'TF' => 'Terres Australes Françaises', + 'TG' => 'Togo', + 'TH' => 'Thaïlande', + 'TJ' => 'Tadjikistan', + 'TK' => 'Tokélaou', + 'TL' => 'Timor oriental', + 'TM' => 'Turkménistan', + 'TN' => 'Tunisie', + 'TO' => 'Tonga', + 'TR' => 'Turquie', + 'TT' => 'Trinité-Et-Tobago', + 'TV' => 'Tuvalu', + 'TW' => 'Taïwan', + 'TZ' => 'Tanzanie, République Unie De', + 'UA' => 'Ukraine', + 'UG' => 'Ouganda', + 'UM' => 'Îles Mineures Éloignées Des États-Unis', + 'US' => 'États-Unis', + 'UY' => 'Uruguay', + 'UZ' => 'Ouzbékistan', + 'VA' => 'Saint-Siège (État de la Cité du Vatican)', + 'VC' => 'Saint-Vincent-Et-Les-Grenadines', + 'VE' => 'Venezuela, République Bolivarienne Du', + 'VG' => 'Îles Vierges Britanniques', + 'VI' => 'Îles Vierges des États-Unis', + 'VN' => 'Vietnam', + 'VU' => 'Vanuatu', + 'WF' => 'Wallis-Et-Futuna', + 'WS' => 'Samoa', + 'YE' => 'Yémen', + 'YT' => 'Mayotte', + 'ZA' => 'Afrique Du Sud', + 'ZM' => 'Zambie', + 'ZW' => 'Zimbabwe', +]; diff --git a/modules/Admin/Language/fr2/Dashboard.php b/modules/Admin/Language/fr2/Dashboard.php new file mode 100644 index 00000000..881073fd --- /dev/null +++ b/modules/Admin/Language/fr2/Dashboard.php @@ -0,0 +1,28 @@ + 'Admin dashboard', + 'welcome_message' => 'Welcome to the admin area!', + 'podcasts' => [ + 'title' => 'Podcasts', + 'not_found' => 'No published podcast', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'episodes' => [ + 'title' => 'Episodes', + 'not_found' => 'No published episode', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'storage' => [ + 'title' => 'Storage', + 'subtitle' => '{totalUploaded} out of {totalStorage}', + ], +]; diff --git a/modules/Admin/Language/fr2/Episode.php b/modules/Admin/Language/fr2/Episode.php new file mode 100644 index 00000000..98838164 --- /dev/null +++ b/modules/Admin/Language/fr2/Episode.php @@ -0,0 +1,225 @@ + 'Saison {seasonNumber}', + 'season_abbr' => 'S{seasonNumber}', + 'number' => 'Épisode {episodeNumber}', + 'number_abbr' => 'Ép. {episodeNumber}', + 'season_episode' => 'Saison {seasonNumber} épisode {episodeNumber}', + 'season_episode_abbr' => 'S{seasonNumber}:E{episodeNumber}', + 'number_of_comments' => '{numberOfComments, plural, + one {# commentaire} + other {# commentaires} + }', + 'all_podcast_episodes' => 'Tous les épisodes du podcast', + 'back_to_podcast' => 'Revenir au podcast', + 'edit' => 'Modifier', + 'preview' => 'Preview', + 'publish' => 'Publier', + 'publish_edit' => 'Modifier la publication', + 'publish_date_edit' => 'Modifier la date de publication', + 'unpublish' => 'Dépublier', + 'publish_error' => 'L’épisode est déjà publié.', + 'publish_edit_error' => 'L’épisode est déjà publié.', + 'publish_cancel_error' => 'L’épisode est déjà publié.', + 'publish_date_edit_error' => 'L\'épisode n\'a pas encore été publié, vous ne pouvez pas modifier sa date de publication.', + 'publish_date_edit_future_error' => 'La date de publication de l\'épisode ne peut être définie qu\'à une date antérieure ! Si vous souhaitez la replanifier, dépubliez-le d\'abord.', + 'publish_date_edit_success' => 'La date de publication de l\'épisode a été mise à jour avec succès !', + 'unpublish_error' => 'L’épisode n’est pas publié.', + 'delete' => 'Supprimer', + 'go_to_page' => 'Voir', + 'create' => 'Ajouter un épisode', + 'publication_status' => [ + 'published' => 'Publié', + 'with_podcast' => 'Publié', + 'scheduled' => 'Planifié', + 'not_published' => 'Non publié', + ], + 'with_podcast_hint' => 'Publier en même temps que le podcast', + 'list' => [ + 'search' => [ + 'placeholder' => 'Rechercher un épisode', + 'clear' => 'Effacer la recherche', + 'submit' => 'Recherche', + ], + 'number_of_episodes' => '{numberOfEpisodes, plural, + one {# épisode} + other {# épisodes} + }', + 'episode' => 'Épisode', + 'visibility' => 'Visibilité', + 'downloads' => 'Downloads', + 'comments' => 'Commentaires', + 'actions' => 'Actions', + ], + 'messages' => [ + 'createSuccess' => 'L’épisode a été créé avec succès  !', + 'editSuccess' => 'L’épisode a bien été mis à jour  !', + 'publishSuccess' => '{publication_status, select, + published {Épisode publié avec succès !} + scheduled {La publication de l\'épisode est planifiée avec succès !} + with_podcast {Cet épisode sera publié en même temps que le podcast.} + other {Cet épisode n\'est pas publié.} + }', + 'publishCancelSuccess' => 'La publication de l’épisode a bien été annulée  !', + 'unpublishBeforeDeleteTip' => 'Vous devez dépublier l\'épisode avant de le supprimer.', + 'scheduleDateError' => 'La date de planification doit être définie !', + 'deletePublishedEpisodeError' => 'Vous devez dépublier l\'épisode avant de le supprimer.', + 'deleteSuccess' => 'L\'épisode a bien été supprimé !', + 'deleteError' => 'Impossible de supprimer {type, select, + transcript {la transcription} + chapters {les chapitres} + image {la couverture} + audio {l\'audio} + other {le média} + } de l\'épisode.', + 'deleteFileError' => 'Failed to delete {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + } file {file_key}. You may manually remove it from your disk.', + 'sameSlugError' => 'Il existe déjà un épisode avec le slug choisi.', + ], + 'form' => [ + 'file_size_error' => + 'Your file size is too big! Max size is {0}. Increase the `memory_limit`, `upload_max_filesize` and `post_max_size` values in your php configuration file then restart your web server to upload your file.', + 'audio_file' => 'Audio file', + 'audio_file_hint' => 'Choose an .mp3 or .m4a audio file.', + 'info_section_title' => 'Episode info', + 'cover' => 'Episode cover', + 'cover_hint' => + 'If you do not set a cover, the podcast cover will be used instead.', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'title' => 'Title', + 'title_hint' => + 'Should contain a clear and concise episode name. Do not specify the episode or season numbers here.', + 'permalink' => 'Permalink', + 'season_number' => 'Season', + 'episode_number' => 'Episode', + 'type' => [ + 'label' => 'Type', + 'full' => 'Full', + 'full_hint' => 'Complete content (the episode)', + 'trailer' => 'Trailer', + 'trailer_hint' => 'Short, promotional piece of content that represents a preview of the current show', + 'bonus' => 'Bonus', + 'bonus_hint' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show', + ], + 'premium_title' => 'Premium', + 'premium' => 'Episode must be accessible to premium subscribers only', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does the episode contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'show_notes_section_title' => 'Show notes', + 'show_notes_section_subtitle' => + 'Up to 4000 characters, be clear and concise. Show notes help potential listeners in finding the episode.', + 'description' => 'Description', + 'description_footer' => 'Description footer', + 'description_footer_hint' => + 'This text is added at the end of each episode description, it is a good place to input your social links for example.', + 'additional_files_section_title' => 'Additional files', + 'additional_files_section_subtitle' => + 'These files may be used by other platforms to provide better experience to your audience. See the {podcastNamespaceLink} for more information.', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this episode about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real or fictional location', + 'transcript' => 'Transcript (subtitles / closed captions)', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', + 'transcript_download' => 'Download transcript', + 'transcript_file' => 'Transcript file (.srt or .vtt)', + 'transcript_remote_url' => 'Remote url for transcript', + 'transcript_file_delete' => 'Delete transcript file', + 'chapters' => 'Chapitrage', + 'chapters_hint' => 'Le fichier doit être en format “JSON Chapters”.', + 'chapters_download' => 'Télécharger le chapitrage', + 'chapters_file' => 'Fichier de chapitrage', + 'chapters_remote_url' => 'URL distante pour le fichier de chapitrage', + 'chapters_file_delete' => 'Supprimer le fichier de chapitrage', + 'advanced_section_title' => 'Paramètres avancés', + 'advanced_section_subtitle' => + 'Si vous avez besoin d’une balise RSS que Castopod ne couvre pas, définissez-la ici.', + 'custom_rss' => 'Balises RSS personnalisées pour l’épisode', + 'custom_rss_hint' => 'Ceci sera injecté dans la balise ❬item❭.', + 'block' => 'L\'épisode doit être masqué dans les catalogues publics', + 'block_hint' => + 'Statut caché ou visible de l\'épisode : activer ceci empêche l\'épisode d\'apparaître dans les Apple Podcasts, Google Podcasts, et toutes les applications tierces qui utilisent ces répertoires. (Sans garantie)', + 'submit_create' => 'Créer l’épisode', + 'submit_edit' => 'Enregistrer l’épisode', + ], + 'publish_form' => [ + 'back_to_episode_dashboard' => 'Retour au tableau de bord de l’épisode', + 'post' => 'Votre message de publication', + 'post_hint' => + "Écrivez un message pour annoncer la publication de votre épisode. Ce message sera diffusé à toutes les personnes qui vous suivent dans le fédiverse et mis en évidence sur la page d’accueil de votre podcast.", + 'message_placeholder' => 'Entrez votre message…', + 'publication_date' => 'Date de publication', + 'publication_method' => [ + 'now' => 'Maintenant', + 'schedule' => 'Planifier', + 'with_podcast' => 'Publier en même temps que le podcast', + ], + 'scheduled_publication_date' => 'Date de publication programmée', + 'scheduled_publication_date_clear' => 'Effacer la date de publication', + 'scheduled_publication_date_hint' => + 'Vous pouvez planifier la sortie de l’épisode en saisissant une date de publication future. Ce champ doit être au format YYYY-MM-DD HH:mm', + 'submit' => 'Publier', + 'submit_edit' => 'Modifier la publication', + 'cancel_publication' => 'Annuler la publication', + 'message_warning' => 'Vous n’avez pas saisi de message pour l’annonce de votre épisode  !', + 'message_warning_hint' => 'Ajouter un message augmente l’engagement sur les réseaux sociaux, donnant une meilleure visibilité à votre épisode.', + 'message_warning_submit' => 'Publier quand même', + ], + 'publish_date_edit_form' => [ + 'new_publication_date' => 'Nouvelle date de publication', + 'new_publication_date_hint' => 'Doit être défini à une date antérieure.', + 'submit' => 'Modifier la date de publication', + ], + 'unpublish_form' => [ + 'disclaimer' => + "Dépublier l'épisode supprimera tous les commentaires et messages qui lui sont associés et le retirera du flux RSS du podcast.", + 'understand' => 'Je comprends, je veux dépublier l’épisode', + 'submit' => 'Dépublier', + ], + 'delete_form' => [ + 'disclaimer' => + "La suppression de l'épisode supprimera tous les fichiers multimédia, les commentaires, les clips vidéo et les parties sonores qui y sont associés.", + 'understand' => 'Je comprends, je veux supprimer l’épisode', + 'submit' => 'Supprimer', + ], + 'embed' => [ + 'title' => 'Lecteur intégré', + 'label' => + 'Sélectionnez une couleur de thème, copiez le code dans le presse-papier, puis collez-le sur votre site internet.', + 'clipboard_iframe' => 'Copier le code du lecteur intégré dans le presse papier', + 'clipboard_url' => 'Copier l’adresse dans le presse papier', + 'dark' => 'Sombre', + 'dark-transparent' => 'Sombre transparent', + 'light' => 'Clair', + 'light-transparent' => 'Clair transparent', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], +]; diff --git a/modules/Admin/Language/fr2/EpisodeNavigation.php b/modules/Admin/Language/fr2/EpisodeNavigation.php new file mode 100644 index 00000000..2f7ff829 --- /dev/null +++ b/modules/Admin/Language/fr2/EpisodeNavigation.php @@ -0,0 +1,23 @@ + 'Aller à la page de l’épisode', + 'dashboard' => 'Tableau de bord de l’épisode', + 'episode-view' => 'Accueil', + 'episode-edit' => 'Modifier l’épisode', + 'episode-persons-manage' => 'Gérer les intervenants', + 'embed-add' => 'Lecteur intégré', + 'clips' => 'Extraits', + 'video-clips-list' => 'Extraits vidéo', + 'video-clips-create' => 'Nouvel extrait vidéo', + 'soundbites-list' => 'Extraits sonores', + 'soundbites-create' => 'Nouvel extrait sonore', +]; diff --git a/modules/Admin/Language/fr2/Fediverse.php b/modules/Admin/Language/fr2/Fediverse.php new file mode 100644 index 00000000..3e7a0b26 --- /dev/null +++ b/modules/Admin/Language/fr2/Fediverse.php @@ -0,0 +1,32 @@ + [ + 'actorNotFound' => 'L’utilisateur n’a pu être trouvé  !', + 'blockActorSuccess' => '{actor} a été bloqué  !', + 'unblockActorSuccess' => 'L’utilisateur a été débloqué  !', + 'blockDomainSuccess' => '{domain} a été bloqué  !', + 'unblockDomainSuccess' => '{domain} a été débloqué  !', + ], + 'blocked_actors' => 'Utilisateurs bloqués', + 'blocked_domains' => 'Domaines bloqués', + 'block_lists_form' => [ + 'handle' => 'Identifiant', + 'handle_hint' => 'Saisissez l’utilisateur @nom@domaine.', + 'domain' => 'Nom de domaine', + 'submit' => 'Bloquer !', + ], + 'list' => [ + 'actor' => 'Utilisateur', + 'domain' => 'Nom de domaine', + 'unblock' => 'Débloquer', + ], +]; diff --git a/modules/Admin/Language/fr2/Home.php b/modules/Admin/Language/fr2/Home.php new file mode 100644 index 00000000..0ec83396 --- /dev/null +++ b/modules/Admin/Language/fr2/Home.php @@ -0,0 +1,14 @@ + 'Tous les podcasts', + 'no_podcast' => 'Aucun podcast trouvé', +]; diff --git a/modules/Admin/Language/fr2/Install.php b/modules/Admin/Language/fr2/Install.php new file mode 100644 index 00000000..b7d6ea1b --- /dev/null +++ b/modules/Admin/Language/fr2/Install.php @@ -0,0 +1,61 @@ + 'Configuration manuelle', + 'manual_config_subtitle' => + 'Créez un fichier `.env` qui contient tous vos paramètres puis rafraîchissez la page pour continuer l’installation.', + 'form' => [ + 'instance_config' => 'Paramètres de l’instance', + 'hostname' => 'Nom d’hôte', + 'media_base_url' => 'Adresse racine des médias', + 'media_base_url_hint' => + 'Si vous utilisez un CDN et/ou un service de mesure d’audience externe, vous pouvez les définir ici.', + 'admin_gateway' => 'Adresse d’administration', + 'admin_gateway_hint' => + 'Chemin pour accéder à l’administration (par exemple https://example.com/cp-admin). Il est défini par défaut à cp-admin, nous vous recommandons de le changer par mesure de sécurité.', + 'auth_gateway' => 'Adresse d’authentification', + 'auth_gateway_hint' => + 'Le chemin des pages d’authentication (par exemple https://example.fr/cp-auth). Il est défini par défaut à cp-auth, nous vous recommandons de le changer par mesure de sécurité.', + 'database_config' => 'Paramètres de la base de données', + 'database_config_hint' => + 'Castopod doit se connecter à votre base de données MySQL (ou MariaDB). Si vous ne disposez pas de ces informations, merci de contacter l’administrateur du serveur.', + 'db_hostname' => 'Nom d’hôte (ou IP) de la base de données', + 'db_name' => 'Nom de la base de données', + 'db_username' => 'Utilisateur de la base de données', + 'db_password' => 'Mot de passe de la base de données', + 'db_prefix' => 'Préfixe des tables', + 'db_prefix_hint' => + "Le préfixe des noms de tables de Castopod, laissez la valeur par défaut si vous ne savez pas de quoi il s’agit.", + 'cache_config' => 'Paramètres du cache', + 'cache_config_hint' => + 'Sélectionnez votre gestionnaire de cache préféré. Laissez la valeur par défaut si vous ne savez pas de quoi il s’agit.', + 'cache_handler' => 'Gestionnaire de cache', + 'cacheHandlerOptions' => [ + 'file' => 'Fichier', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Suivant', + 'submit' => 'Terminer l’installation', + 'create_superadmin' => 'Create your superadmin account', + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Your superadmin account has been created successfully. Login to start podcasting!', + 'databaseConnectError' => + 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'writeError' => + "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + ], +]; diff --git a/modules/Admin/Language/fr2/Navigation.php b/modules/Admin/Language/fr2/Navigation.php new file mode 100644 index 00000000..f3ffb129 --- /dev/null +++ b/modules/Admin/Language/fr2/Navigation.php @@ -0,0 +1,44 @@ + 'Toggle sidebar', + 'go_to_website' => 'Go to website', + 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', + 'dashboard' => 'Dashboard', + 'admin' => 'Home', + 'podcasts' => 'Podcasts', + 'podcast-list' => 'All podcasts', + 'podcast-create' => 'New podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', + 'persons' => 'Persons', + 'person-list' => 'All persons', + 'person-create' => 'New person', + 'fediverse' => 'Fediverse', + 'fediverse-blocked-actors' => 'Blocked accounts', + 'fediverse-blocked-domains' => 'Blocked domains', + 'users' => 'Users', + 'user-list' => 'All users', + 'user-create' => 'New user', + 'pages' => 'Pages', + 'page-list' => 'All pages', + 'page-create' => 'New Page', + 'settings' => 'Settings', + 'settings-general' => 'General', + 'settings-theme' => 'Theme', + 'admin-about' => 'About', + 'account' => [ + 'my-account' => 'My account', + 'change-password' => 'Change password', + 'logout' => 'Logout', + ], +]; diff --git a/modules/Admin/Language/fr2/Notifications.php b/modules/Admin/Language/fr2/Notifications.php new file mode 100644 index 00000000..2b139d51 --- /dev/null +++ b/modules/Admin/Language/fr2/Notifications.php @@ -0,0 +1,19 @@ + 'Notifications', + 'reply' => '{actor_username} replied to your post', + 'favourite' => '{actor_username} favourited your post', + 'reblog' => '{actor_username} shared your post', + 'follow' => '{actor_username} started following you', + 'no_notifications' => 'No notifications', + 'mark_all_as_read' => 'Mark all as read', +]; diff --git a/modules/Admin/Language/fr2/Page.php b/modules/Admin/Language/fr2/Page.php new file mode 100644 index 00000000..b6f49de5 --- /dev/null +++ b/modules/Admin/Language/fr2/Page.php @@ -0,0 +1,30 @@ + 'Back to home', + 'page' => 'Page', + 'all_pages' => 'All pages', + 'create' => 'New page', + 'go_to_page' => 'Go to page', + 'edit' => 'Edit page', + 'delete' => 'Delete page', + 'form' => [ + 'title' => 'Title', + 'permalink' => 'Permalink', + 'content' => 'Content', + 'submit_create' => 'Create page', + 'submit_edit' => 'Save', + ], + 'messages' => [ + 'createSuccess' => 'The page “{pageTitle}” was created successfully!', + 'editSuccess' => 'The page was successfully updated!', + ], +]; diff --git a/modules/Admin/Language/fr2/Pager.php b/modules/Admin/Language/fr2/Pager.php new file mode 100644 index 00000000..e25ee638 --- /dev/null +++ b/modules/Admin/Language/fr2/Pager.php @@ -0,0 +1,21 @@ + 'Page navigation', + 'first' => 'First', + 'previous' => 'Previous', + 'next' => 'Next', + 'last' => 'Last', + 'older' => 'Older', + 'newer' => 'Newer', + 'invalidTemplate' => '{0} is not a valid Pager template.', + 'invalidPaginationGroup' => '{0} is not a valid Pagination group.', +]; diff --git a/modules/Admin/Language/fr2/Person.php b/modules/Admin/Language/fr2/Person.php new file mode 100644 index 00000000..a652be9f --- /dev/null +++ b/modules/Admin/Language/fr2/Person.php @@ -0,0 +1,65 @@ + 'Persons', + 'all_persons' => 'All persons', + 'no_person' => 'Nobody found!', + 'create' => 'Create a person', + 'view' => 'View person', + 'edit' => 'Edit person', + 'delete' => 'Delete person', + 'messages' => [ + 'createSuccess' => 'Person has been successfully created!', + 'editSuccess' => 'Person has been successfully updated!', + 'deleteSuccess' => 'Person has been removed!', + ], + 'form' => [ + 'avatar' => 'Avatar', + 'avatar_size_hint' => + 'Avatar must be squared and at least 400px wide and tall.', + 'full_name' => 'Full name', + 'full_name_hint' => 'This is the full name or alias of the person.', + 'unique_name' => 'Unique name', + 'unique_name_hint' => 'Used for URLs', + 'information_url' => 'Information URL', + 'information_url_hint' => + 'Url to a relevant resource of information about the person, such as a homepage or third-party profile platform.', + 'submit_create' => 'Create person', + 'submit_edit' => 'Save person', + ], + 'podcast_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this podcast', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'episode_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this episode', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'credits' => 'Credits', +]; diff --git a/modules/Admin/Language/fr2/Platforms.php b/modules/Admin/Language/fr2/Platforms.php new file mode 100644 index 00000000..e161181c --- /dev/null +++ b/modules/Admin/Language/fr2/Platforms.php @@ -0,0 +1,43 @@ + [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', + 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', + 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', + 'visible' => 'Display in podcast homepage?', + 'on_embed' => 'Display on embeddable player?', + 'remove' => 'Remove {platformName}', + 'submit' => 'Save', + 'messages' => [ + 'updateSuccess' => 'Platform links have been successfully updated!', + 'removeLinkSuccess' => 'The platform link has been removed.', + 'removeLinkError' => + 'The platform link could not be removed. Try again.', + ], + 'description' => [ + 'podcasting' => 'The podcast ID on this platform', + 'social' => 'The podcast account ID on this platform', + 'funding' => 'Call to action message', + ], +]; diff --git a/modules/Admin/Language/fr2/Podcast.php b/modules/Admin/Language/fr2/Podcast.php new file mode 100644 index 00000000..ff0daebc --- /dev/null +++ b/modules/Admin/Language/fr2/Podcast.php @@ -0,0 +1,330 @@ + 'All podcasts', + 'no_podcast' => 'No podcast found!', + 'create' => 'Create podcast', + 'import' => 'Import podcast', + 'all_imports' => 'Podcast imports', + 'new_episode' => 'New Episode', + 'view' => 'View podcast', + 'edit' => 'Edit podcast', + 'publish' => 'Publish podcast', + 'publish_edit' => 'Edit publication', + 'delete' => 'Delete podcast', + 'see_episodes' => 'See episodes', + 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', + 'go_to_page' => 'Go to page', + 'latest_episodes' => 'Latest episodes', + 'see_all_episodes' => 'See all episodes', + 'draft' => 'Draft', + 'messages' => [ + 'createSuccess' => 'Podcast successfully created!', + 'editSuccess' => 'Podcast has been successfully updated!', + 'importSuccess' => 'Podcast has been successfully imported!', + 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', + 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, + cover {cover} + banner {banner} + other {media} + }.', + 'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.', + 'podcastFeedUpdateSuccess' => 'Successful update: {number_of_new_episodes, plural, + one {# episode was} + other {# episodes were} + } added to the podcast!', + 'podcastFeedUpToDate' => 'Podcast is already up to date.', + 'publishError' => 'This podcast is either already published or scheduled for publication.', + 'publishEditError' => 'This podcast is not scheduled for publication.', + 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', + 'scheduleDateError' => 'Schedule date must be set!', + ], + 'form' => [ + 'identity_section_title' => 'Podcast identity', + 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + + 'cover' => 'Podcast cover', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'banner' => 'Podcast banner', + 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', + 'banner_delete' => 'Delete podcast banner', + 'title' => 'Title', + 'handle' => 'Handle', + 'handle_hint' => + 'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.', + 'type' => [ + 'label' => 'Type', + 'episodic' => 'Episodic', + 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', + 'serial' => 'Serial', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + ], + 'description' => 'Description', + 'classification_section_title' => 'Classification', + 'classification_section_subtitle' => + 'These fields will impact your audience and competition.', + 'language' => 'Language', + 'category' => 'Category', + 'category_placeholder' => 'Select a category…', + 'other_categories' => 'Other categories', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does it contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'author_section_title' => 'Author', + 'author_section_subtitle' => 'Who is managing the podcast?', + 'owner_name' => 'Owner name', + 'owner_name_hint' => + 'For administrative use only. Visible in the public RSS feed.', + 'owner_email' => 'Owner email', + 'owner_email_hint' => + 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', + 'publisher' => 'Publisher', + 'publisher_hint' => + 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', + 'copyright' => 'Copyright', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this podcast about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real place or fictional', + 'monetization_section_title' => 'Monetization', + 'monetization_section_subtitle' => + 'Earn money thanks to your audience.', + 'premium' => 'Premium', + 'premium_by_default' => 'Episodes must be set as premium by default', + 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', + 'payment_pointer' => 'Payment Pointer for Web Monetization', + 'payment_pointer_hint' => + 'This is your where you will receive money thanks to Web Monetization', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the podcast', + 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', + 'new_feed_url' => 'New feed URL', + 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', + 'old_feed_url' => 'Old feed URL', + 'partnership' => 'Partnership', + 'partner_id' => 'ID', + 'partner_link_url' => 'Link URL', + 'partner_image_url' => 'Image URL', + 'partner_id_hint' => 'Your own partner ID', + 'partner_link_url_hint' => 'The generic partner link address', + 'partner_image_url_hint' => 'The generic partner image address', + 'block' => 'Podcast should be hidden from public catalogues', + 'block_hint' => + 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'complete' => 'Podcast will not be having new episodes', + 'lock' => 'Prevent podcast from being copied', + 'lock_hint' => + 'The purpose is to tell other podcast platforms whether they are allowed to import this feed. A value of yes means that any attempt to import this feed into a new platform should be rejected.', + 'submit_create' => 'Create podcast', + 'submit_edit' => 'Save podcast', + ], + 'category_options' => [ + 'uncategorized' => 'uncategorized', + 'arts' => 'Arts', + 'business' => 'Business', + 'comedy' => 'Comedy', + 'education' => 'Education', + 'fiction' => 'Fiction', + 'government' => 'Government', + 'health_and_fitness' => 'Health & Fitness', + 'history' => 'History', + 'kids_and_family' => 'Kids & Family', + 'leisure' => 'Leisure', + 'music' => 'Music', + 'news' => 'News', + 'religion_and_spirituality' => 'Religion & Spirituality', + 'science' => 'Science', + 'society_and_culture' => 'Society & Culture', + 'sports' => 'Sports', + 'technology' => 'Technology', + 'true_crime' => 'True Crime', + 'tv_and_film' => 'TV & Film', + 'books' => 'Books', + 'design' => 'Design', + 'fashion_and_beauty' => 'Fashion & Beauty', + 'food' => 'Food', + 'performing_arts' => 'Performing Arts', + 'visual_arts' => 'Visual Arts', + 'careers' => 'Careers', + 'entrepreneurship' => 'Entrepreneurship', + 'investing' => 'Investing', + 'management' => 'Management', + 'marketing' => 'Marketing', + 'non_profit' => 'Non-Profit', + 'comedy_interviews' => 'Comedy Interviews', + 'improv' => 'Improv', + 'stand_up' => 'Stand-Up', + 'courses' => 'Courses', + 'how_to' => 'How To', + 'language_learning' => 'Language Learning', + 'self_improvement' => 'Self-Improvement', + 'comedy_fiction' => 'Comedy Fiction', + 'drama' => 'Drama', + 'science_fiction' => 'Science Fiction', + 'alternative_health' => 'Alternative Health', + 'fitness' => 'Fitness', + 'medicine' => 'Medicine', + 'mental_health' => 'Mental Health', + 'nutrition' => 'Nutrition', + 'sexuality' => 'Sexuality', + 'education_for_kids' => 'Education for Kids', + 'parenting' => 'Parenting', + 'pets_and_animals' => 'Pets & Animals', + 'stories_for_kids' => 'Stories for Kids', + 'animation_and_manga' => 'Animation & Manga', + 'automotive' => 'Automotive', + 'aviation' => 'Aviation', + 'crafts' => 'Crafts', + 'games' => 'Games', + 'hobbies' => 'Hobbies', + 'home_and_garden' => 'Home & Garden', + 'video_games' => 'Video Games', + 'music_commentary' => 'Music Commentary', + 'music_history' => 'Music History', + 'music_interviews' => 'Music Interviews', + 'business_news' => 'Business News', + 'daily_news' => 'Daily News', + 'entertainment_news' => 'Entertainment News', + 'news_commentary' => 'News Commentary', + 'politics' => 'Politics', + 'sports_news' => 'Sports News', + 'tech_news' => 'Tech News', + 'buddhism' => 'Buddhism', + 'christianity' => 'Christianity', + 'hinduism' => 'Hinduism', + 'islam' => 'Islam', + 'judaism' => 'Judaism', + 'religion' => 'Religion', + 'spirituality' => 'Spirituality', + 'astronomy' => 'Astronomy', + 'chemistry' => 'Chemistry', + 'earth_sciences' => 'Earth Sciences', + 'life_sciences' => 'Life Sciences', + 'mathematics' => 'Mathematics', + 'natural_sciences' => 'Natural Sciences', + 'nature' => 'Nature', + 'physics' => 'Physics', + 'social_sciences' => 'Social Sciences', + 'documentary' => 'Documentary', + 'personal_journals' => 'Personal Journals', + 'philosophy' => 'Philosophy', + 'places_and_travel' => 'Places & Travel', + 'relationships' => 'Relationships', + 'baseball' => 'Baseball', + 'basketball' => 'Basketball', + 'cricket' => 'Cricket', + 'fantasy_sports' => 'Fantasy Sports', + 'football' => 'Football', + 'golf' => 'Golf', + 'hockey' => 'Hockey', + 'rugby' => 'Rugby', + 'running' => 'Running', + 'soccer' => 'Soccer', + 'swimming' => 'Swimming', + 'tennis' => 'Tennis', + 'volleyball' => 'Volleyball', + 'wilderness' => 'Wilderness', + 'wrestling' => 'Wrestling', + 'after_shows' => 'After Shows', + 'film_history' => 'Film History', + 'film_interviews' => 'Film Interviews', + 'film_reviews' => 'Film Reviews', + 'tv_reviews' => 'TV Reviews', + ], + 'publish_form' => [ + 'back_to_podcast_dashboard' => 'Back to podcast dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'submit' => 'Publish', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', + 'message_warning_submit' => 'Publish anyway', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'not_published' => 'This podcast is not yet published.', + 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", + 'understand' => 'I understand, I want the podcast to be permanently deleted', + 'submit' => 'Delete', + ], + 'by' => 'By {publisher}', + 'season' => 'Season {seasonNumber}', + 'list_of_episodes_year' => '{year} episodes ({episodeCount})', + 'list_of_episodes_season' => + 'Season {seasonNumber} episodes ({episodeCount})', + 'no_episode' => 'No episode found!', + 'follow' => 'Follow', + 'followers' => '{numberOfFollowers, plural, + one {# follower} + other {# followers} + }', + 'posts' => '{numberOfPosts, plural, + one {# post} + other {# posts} + }', + 'activity' => 'Activity', + 'episodes' => 'Episodes', + 'sponsor' => 'Sponsor', + 'funding_links' => 'Funding links for {podcastTitle}', + 'find_on' => 'Find {podcastTitle} on', + 'listen_on' => 'Listen on', +]; diff --git a/modules/Admin/Language/fr2/PodcastNavigation.php b/modules/Admin/Language/fr2/PodcastNavigation.php new file mode 100644 index 00000000..bb777707 --- /dev/null +++ b/modules/Admin/Language/fr2/PodcastNavigation.php @@ -0,0 +1,42 @@ + 'Go to podcast page', + 'rss_feed' => 'RSS feed', + 'dashboard' => 'Podcast dashboard', + 'podcast-view' => 'Home', + 'podcast-edit' => 'Edit podcast', + 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', + 'episodes' => 'Episodes', + 'episode-list' => 'All episodes', + 'episode-create' => 'New episode', + 'analytics' => 'Analytics', + 'podcast-analytics' => 'Audience overview', + 'podcast-analytics-webpages' => 'Web pages visits', + 'podcast-analytics-locations' => 'Locations', + 'podcast-analytics-unique-listeners' => 'Unique listeners', + 'podcast-analytics-players' => 'Players', + 'podcast-analytics-listening-time' => 'Listening time', + 'podcast-analytics-time-periods' => 'Time periods', + 'monetization' => 'Monetization', + 'subscription-list' => 'All subscriptions', + 'subscription-create' => 'Add subscription', + 'contributors' => 'Contributors', + 'contributor-list' => 'All contributors', + 'contributor-add' => 'Add contributor', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', + 'platforms-social' => 'Social networks', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', +]; diff --git a/modules/Admin/Language/fr2/Settings.php b/modules/Admin/Language/fr2/Settings.php new file mode 100644 index 00000000..4a70dcba --- /dev/null +++ b/modules/Admin/Language/fr2/Settings.php @@ -0,0 +1,58 @@ + 'General settings', + 'instance' => [ + 'title' => 'Instance', + 'site_icon' => 'Site icon', + 'site_icon_delete' => 'Delete site icon', + 'site_icon_hint' => 'Site icons are what you see on your browser tabs, bookmarks bar, and when you add a website as a shortcut on mobile devices.', + 'site_icon_helper' => 'Icon must be squared and at least 512px wide and tall.', + 'site_name' => 'Site name', + 'site_description' => 'Site description', + 'submit' => 'Save', + 'editSuccess' => 'Instance has been updated successfully!', + 'deleteIconSuccess' => 'Site icon has been remove successfully!', + ], + 'images' => [ + 'title' => 'Images', + 'subtitle' => 'Here you can regenerate all images based on the originals that were uploaded. To be used if you find that some images are missing. This task may take a while.', + 'regenerate' => 'Regenerate images', + 'regenerationSuccess' => 'All images have been regenerated successfully!', + ], + 'housekeeping' => [ + 'title' => 'Housekeeping', + 'subtitle' => 'Runs various housekeeping tasks. Use this feature if you ever encounter issues with media files or data integrity. These tasks may take a while.', + 'reset_counts' => 'Reset counts', + 'reset_counts_helper' => 'This option will recalculate and reset all data counts (number of followers, posts, comments, …).', + 'rewrite_media' => 'Rewrite media metadata', + 'rewrite_media_helper' => 'This option will delete all superfluous media files and recreate them (images, audio files, transcripts, chapters, …)', + 'rename_episodes_files' => 'Rename episode audio files', + 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', + 'clear_cache' => 'Clear all cache', + 'clear_cache_helper' => 'This option will flush redis cache or writable/cache files.', + 'run' => 'Run housekeeping', + 'runSuccess' => 'Housekeeping has been run successfully!', + ], + 'theme' => [ + 'title' => 'Theme', + 'accent_section_title' => 'Accent color', + 'accent_section_subtitle' => 'Choose the color to determine the look and feel of all public pages.', + 'pine' => 'Pine', + 'crimson' => 'Crimson', + 'amber' => 'Amber', + 'lake' => 'Lake', + 'jacaranda' => 'Jacaranda', + 'onyx' => 'Onyx', + 'submit' => 'Save', + 'setInstanceThemeSuccess' => 'Theme has been updated successfully!', + ], +]; diff --git a/modules/Admin/Language/fr2/Soundbite.php b/modules/Admin/Language/fr2/Soundbite.php new file mode 100644 index 00000000..a3f828fe --- /dev/null +++ b/modules/Admin/Language/fr2/Soundbite.php @@ -0,0 +1,31 @@ + [ + 'title' => 'Soundbites', + 'soundbite' => 'Soundbite', + ], + 'messages' => [ + 'createSuccess' => 'Soundbite has been successfully created!', + 'deleteSuccess' => 'Soundbite has been successfully removed!', + ], + 'form' => [ + 'title' => 'New soundbite', + 'soundbite_title' => 'Soundbite title', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'submit' => 'Create soundbite', + ], + 'play' => 'Play soundbite', + 'stop' => 'Stop soundbite', + 'create' => 'New soundbite', + 'delete' => 'Delete soundbite', +]; diff --git a/modules/Admin/Language/fr2/Validation.php b/modules/Admin/Language/fr2/Validation.php new file mode 100644 index 00000000..f76c3163 --- /dev/null +++ b/modules/Admin/Language/fr2/Validation.php @@ -0,0 +1,17 @@ + + '{field} is either not an image, or it is not wide or tall enough.', + 'is_image_ratio' => + '{field} is either not an image or not of the right ratio.', + 'is_json' => '{field} contains invalid JSON.', +]; diff --git a/modules/Admin/Language/fr2/VideoClip.php b/modules/Admin/Language/fr2/VideoClip.php new file mode 100644 index 00000000..638de697 --- /dev/null +++ b/modules/Admin/Language/fr2/VideoClip.php @@ -0,0 +1,72 @@ + [ + 'title' => 'Video clips', + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Clip is waiting to be processed.', + 'pending' => 'pending', + 'pending_hint' => 'Clip will be generated shortly.', + 'running' => 'running', + 'running_hint' => 'Clip is being generated.', + 'failed' => 'failed', + 'failed_hint' => 'Clip could not be generated: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Clip was generated successfully!', + ], + 'clip' => 'Clip', + 'duration' => 'Job duration', + ], + 'title' => 'Video clip: {videoClipLabel}', + 'download_clip' => 'Download clip', + 'create' => 'New video clip', + 'go_to_page' => 'Go to clip page', + 'retry' => 'Retry clip generation', + 'delete' => 'Delete clip', + 'logs' => 'Job logs', + 'messages' => [ + 'alreadyExistingError' => 'The video clip you are trying to create already exists!', + 'addToQueueSuccess' => 'Video clip has been added to queue, awaiting to be created!', + 'deleteSuccess' => 'Video clip has been successfully removed!', + ], + 'format' => [ + 'landscape' => 'Landscape', + 'portrait' => 'Portrait', + 'squared' => 'Squared', + ], + 'form' => [ + 'title' => 'New video clip', + 'params_section_title' => 'Video clip parameters', + 'clip_title' => 'Clip title', + 'format' => [ + 'label' => 'Choose a format', + 'landscape_hint' => 'With a 16:9 ratio, landscape videos are great for PeerTube, Youtube and Vimeo.', + 'portrait_hint' => 'With a 9:16 ratio, portrait videos are great for TikTok, Youtube shorts and Instagram stories.', + 'squared_hint' => 'With a 1:1 ratio, squared videos are great for Mastodon, Facebook, Twitter and LinkedIn.', + ], + 'theme' => 'Select a theme', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'trim_start' => 'Trim start', + 'trim_end' => 'Trim end', + 'submit' => 'Create video clip', + ], + 'requirements' => [ + 'title' => 'Missing requirements', + 'missing' => 'You have missing requirements. Make sure to add all the required items to be allowed creating a video for this episode!', + 'ffmpeg' => 'FFmpeg', + 'gd' => 'Graphics Draw (GD)', + 'freetype' => 'Freetype library for GD', + 'transcript' => 'Transcript file (.srt)', + ], +]; diff --git a/modules/Admin/Language/gd/AboutCastopod.php b/modules/Admin/Language/gd/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/gd/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/gd/Breadcrumb.php b/modules/Admin/Language/gd/Breadcrumb.php index f3269bfa..408c9f9f 100644 --- a/modules/Admin/Language/gd/Breadcrumb.php +++ b/modules/Admin/Language/gd/Breadcrumb.php @@ -19,6 +19,7 @@ return [ 'pages' => 'pages', 'settings' => 'settings', 'theme' => 'theme', + 'about' => 'about', 'add' => 'add', 'new' => 'new', 'edit' => 'edit', @@ -28,15 +29,19 @@ return [ 'publish-date-edit' => 'edit publication date', 'unpublish' => 'unpublish', 'delete' => 'delete', + 'remove' => 'remove', 'fediverse' => 'fediverse', - 'block-lists' => 'block lists', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', 'users' => 'users', 'my-account' => 'my account', 'change-password' => 'change password', - 'import' => 'feed import', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', 'platforms' => 'platforms', 'social' => 'social networks', 'funding' => 'funding', + 'monetization-other' => 'other monetization', 'analytics' => 'analytics', 'locations' => 'locations', 'webpages' => 'web pages', diff --git a/modules/Admin/Language/gd/Charts.php b/modules/Admin/Language/gd/Charts.php index 4b33530e..6ede2510 100644 --- a/modules/Admin/Language/gd/Charts.php +++ b/modules/Admin/Language/gd/Charts.php @@ -37,4 +37,5 @@ return [ 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', 'total_storage_by_month' => 'Monthly storage (in MB)', 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', ]; diff --git a/modules/Admin/Language/gd/Common.php b/modules/Admin/Language/gd/Common.php index 596c8bcd..74addcf2 100644 --- a/modules/Admin/Language/gd/Common.php +++ b/modules/Admin/Language/gd/Common.php @@ -40,6 +40,7 @@ return [ ], 'upload_file' => 'Upload a file', 'remote_url' => 'Remote URL', + 'save' => 'Save', ], 'play_episode_button' => [ 'play' => 'Play', diff --git a/modules/Admin/Language/gd/Episode.php b/modules/Admin/Language/gd/Episode.php index 91313a7c..4fa846e3 100644 --- a/modules/Admin/Language/gd/Episode.php +++ b/modules/Admin/Language/gd/Episode.php @@ -22,6 +22,7 @@ return [ 'all_podcast_episodes' => 'All podcast episodes', 'back_to_podcast' => 'Go back to podcast', 'edit' => 'Edit', + 'preview' => 'Preview', 'publish' => 'Publish', 'publish_edit' => 'Edit publication', 'publish_date_edit' => 'Edit publication date', @@ -55,6 +56,7 @@ return [ }', 'episode' => 'Episode', 'visibility' => 'Visibility', + 'downloads' => 'Downloads', 'comments' => 'Comments', 'actions' => 'Actions', ], @@ -85,7 +87,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ @@ -137,9 +139,9 @@ return [ 'location_name' => 'Location name or address', 'location_name_hint' => 'This can be a real or fictional location', 'transcript' => 'Transcript (subtitles / closed captions)', - 'transcript_hint' => 'Only .srt are allowed.', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', 'transcript_download' => 'Download transcript', - 'transcript_file' => 'Transcript file (.srt)', + 'transcript_file' => 'Transcript file (.srt or .vtt)', 'transcript_remote_url' => 'Remote url for transcript', 'transcript_file_delete' => 'Delete transcript file', 'chapters' => 'Chapters', @@ -210,4 +212,14 @@ return [ 'light' => 'Light', 'light-transparent' => 'Light transparent', ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], ]; diff --git a/modules/Admin/Language/gd/Navigation.php b/modules/Admin/Language/gd/Navigation.php index 68d4609d..f3ffb129 100644 --- a/modules/Admin/Language/gd/Navigation.php +++ b/modules/Admin/Language/gd/Navigation.php @@ -12,12 +12,14 @@ return [ 'toggle_sidebar' => 'Toggle sidebar', 'go_to_website' => 'Go to website', 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', 'dashboard' => 'Dashboard', 'admin' => 'Home', 'podcasts' => 'Podcasts', 'podcast-list' => 'All podcasts', 'podcast-create' => 'New podcast', - 'podcast-import' => 'Import a podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', 'persons' => 'Persons', 'person-list' => 'All persons', 'person-create' => 'New person', @@ -33,6 +35,7 @@ return [ 'settings' => 'Settings', 'settings-general' => 'General', 'settings-theme' => 'Theme', + 'admin-about' => 'About', 'account' => [ 'my-account' => 'My account', 'change-password' => 'Change password', diff --git a/modules/Admin/Language/gd/Platforms.php b/modules/Admin/Language/gd/Platforms.php index ab17d599..e161181c 100644 --- a/modules/Admin/Language/gd/Platforms.php +++ b/modules/Admin/Language/gd/Platforms.php @@ -9,9 +9,22 @@ declare(strict_types=1); */ return [ - 'title' => 'Platforms', + 'title' => [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', 'visible' => 'Display in podcast homepage?', 'on_embed' => 'Display on embeddable player?', 'remove' => 'Remove {platformName}', diff --git a/modules/Admin/Language/gd/Podcast.php b/modules/Admin/Language/gd/Podcast.php index 426b763b..ff0daebc 100644 --- a/modules/Admin/Language/gd/Podcast.php +++ b/modules/Admin/Language/gd/Podcast.php @@ -13,6 +13,7 @@ return [ 'no_podcast' => 'No podcast found!', 'create' => 'Create podcast', 'import' => 'Import podcast', + 'all_imports' => 'Podcast imports', 'new_episode' => 'New Episode', 'view' => 'View podcast', 'edit' => 'Edit podcast', @@ -21,6 +22,7 @@ return [ 'delete' => 'Delete podcast', 'see_episodes' => 'See episodes', 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', 'go_to_page' => 'Go to page', 'latest_episodes' => 'Latest episodes', 'see_all_episodes' => 'See all episodes', @@ -48,7 +50,6 @@ return [ other {# episodes were} } added to the podcast!', 'podcastFeedUpToDate' => 'Podcast is already up to date.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', 'publishError' => 'This podcast is either already published or scheduled for publication.', 'publishEditError' => 'This podcast is not scheduled for publication.', 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', @@ -57,6 +58,8 @@ return [ 'form' => [ 'identity_section_title' => 'Podcast identity', 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + 'cover' => 'Podcast cover', 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', 'banner' => 'Podcast banner', @@ -71,7 +74,17 @@ return [ 'episodic' => 'Episodic', 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', 'serial' => 'Serial', - 'serial_hint' => 'If episodes are intended to be consumed in sequential order. The oldest episodes will be presented first.', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', ], 'description' => 'Description', 'classification_section_title' => 'Classification', @@ -96,6 +109,8 @@ return [ 'owner_email' => 'Owner email', 'owner_email_hint' => 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', 'publisher' => 'Publisher', 'publisher_hint' => 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', @@ -110,6 +125,11 @@ return [ 'premium' => 'Premium', 'premium_by_default' => 'Episodes must be set as premium by default', 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', 'payment_pointer' => 'Payment Pointer for Web Monetization', 'payment_pointer_hint' => 'This is your where you will receive money thanks to Web Monetization', @@ -118,11 +138,12 @@ return [ 'If you need RSS tags that Castopod does not handle, set them here.', 'custom_rss' => 'Custom RSS tags for the podcast', 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', 'new_feed_url' => 'New feed URL', 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', 'partnership' => 'Partnership', 'partner_id' => 'ID', 'partner_link_url' => 'Link URL', @@ -130,7 +151,6 @@ return [ 'partner_id_hint' => 'Your own partner ID', 'partner_link_url_hint' => 'The generic partner link address', 'partner_image_url_hint' => 'The generic partner image address', - 'status_section_title' => 'Status', 'block' => 'Podcast should be hidden from public catalogues', 'block_hint' => 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', diff --git a/modules/Admin/Language/gd/PodcastImport.php b/modules/Admin/Language/gd/PodcastImport.php deleted file mode 100644 index 7c3ef67d..00000000 --- a/modules/Admin/Language/gd/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'The podcast to import', - 'old_podcast_section_subtitle' => - 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', - 'imported_feed_url' => 'Feed URL', - 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', - 'new_podcast_section_title' => 'The new podcast', - 'advanced_params_section_title' => 'Advanced parameters', - 'advanced_params_section_subtitle' => - 'Keep the default values if you have no idea of what the fields are for.', - 'slug_field' => 'Field to be used to calculate episode slug', - 'description_field' => - 'Source field used for episode description / show notes', - 'force_renumber' => 'Force episodes renumbering', - 'force_renumber_hint' => - 'Use this if your podcast does not have episode numbers but wish to set them during import.', - 'season_number' => 'Season number', - 'season_number_hint' => - 'Use this if your podcast does not have a season number but wish to set one during import. Leave blank otherwise.', - 'max_episodes' => 'Maximum number of episodes to import', - 'max_episodes_hint' => 'Leave blank to import all episodes', - 'lock_import' => - 'This feed is protected. You cannot import it. If you are the owner, unprotect it on the origin platform.', - 'submit' => 'Import podcast', -]; diff --git a/modules/Admin/Language/gd/PodcastNavigation.php b/modules/Admin/Language/gd/PodcastNavigation.php index b4d7ddc0..bb777707 100644 --- a/modules/Admin/Language/gd/PodcastNavigation.php +++ b/modules/Admin/Language/gd/PodcastNavigation.php @@ -10,10 +10,13 @@ declare(strict_types=1); return [ 'go_to_page' => 'Go to podcast page', + 'rss_feed' => 'RSS feed', 'dashboard' => 'Podcast dashboard', 'podcast-view' => 'Home', 'podcast-edit' => 'Edit podcast', 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', 'episodes' => 'Episodes', 'episode-list' => 'All episodes', 'episode-create' => 'New episode', @@ -25,14 +28,15 @@ return [ 'podcast-analytics-players' => 'Players', 'podcast-analytics-listening-time' => 'Listening time', 'podcast-analytics-time-periods' => 'Time periods', - 'premium' => 'Premium', + 'monetization' => 'Monetization', 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', + 'subscription-create' => 'Add subscription', 'contributors' => 'Contributors', 'contributor-list' => 'All contributors', 'contributor-add' => 'Add contributor', - 'platforms' => 'External platforms', - 'platforms-podcasting' => 'Podcasting', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', 'platforms-social' => 'Social networks', - 'platforms-funding' => 'Funding', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', ]; diff --git a/modules/Admin/Language/gd/Validation.php b/modules/Admin/Language/gd/Validation.php index 750b1968..f76c3163 100644 --- a/modules/Admin/Language/gd/Validation.php +++ b/modules/Admin/Language/gd/Validation.php @@ -13,6 +13,5 @@ return [ '{field} is either not an image, or it is not wide or tall enough.', 'is_image_ratio' => '{field} is either not an image or not of the right ratio.', - 'validate_url' => - 'The {field} field must be a valid URL (eg. https://example.com/).', + 'is_json' => '{field} contains invalid JSON.', ]; diff --git a/modules/Admin/Language/gl/AboutCastopod.php b/modules/Admin/Language/gl/AboutCastopod.php new file mode 100644 index 00000000..3c26b13f --- /dev/null +++ b/modules/Admin/Language/gl/AboutCastopod.php @@ -0,0 +1,22 @@ + 'Acerca de Castopod', + 'host_name' => 'Nome do servidor', + 'version' => 'Versión de Castopod', + 'php_version' => 'Versión de PHP', + 'os' => 'Sistema Operativo', + 'languages' => 'Idiomas', + 'update_database' => 'Anovar base de datos', + 'messages' => [ + 'databaseUpdateSuccess' => 'A base de datos está ao día!', + ], +]; diff --git a/modules/Admin/Language/gl/Breadcrumb.php b/modules/Admin/Language/gl/Breadcrumb.php index 7aa07fba..fa515f15 100644 --- a/modules/Admin/Language/gl/Breadcrumb.php +++ b/modules/Admin/Language/gl/Breadcrumb.php @@ -14,29 +14,34 @@ return [ ->gateway => 'Inicio', 'podcasts' => 'podcasts', 'episodes' => 'episodios', - 'subscriptions' => 'subscriptions', + 'subscriptions' => 'subscricións', 'contributors' => 'contribúen', 'pages' => 'páxinas', 'settings' => 'axustes', 'theme' => 'decorado', + 'about' => 'acerca de', 'add' => 'engadir', 'new' => 'novo', 'edit' => 'editar', 'persons' => 'persoas', 'publish' => 'publicar', 'publish-edit' => 'editar publicación', - 'publish-date-edit' => 'edit publication date', + 'publish-date-edit' => 'editar data de publicación', 'unpublish' => 'retirar publicación', 'delete' => 'eliminar', + 'remove' => 'eliminar', 'fediverse' => 'fediverso', - 'block-lists' => 'listas de bloqueo', + 'blocked-actors' => 'contas bloqueadas', + 'blocked-domains' => 'dominios bloqueados', 'users' => 'usuarias', 'my-account' => 'a miña conta', 'change-password' => 'cambiar contrasinal', - 'import' => 'importar fonte', + 'imports' => 'importacións', + 'sync-feeds' => 'sincronizar fontes', 'platforms' => 'plataformas', 'social' => 'redes sociais', 'funding' => 'financiamento', + 'monetization-other' => 'outros xeitos', 'analytics' => 'análise', 'locations' => 'localizacións', 'webpages' => 'páxinas web', @@ -48,5 +53,5 @@ return [ 'video-clips' => 'clips de vídeo', 'embed' => 'reprodutor para incluír', 'notifications' => 'notificacións', - 'suspend' => 'suspend', + 'suspend' => 'suspender', ]; diff --git a/modules/Admin/Language/gl/Charts.php b/modules/Admin/Language/gl/Charts.php index edd2f8af..0e232ad4 100644 --- a/modules/Admin/Language/gl/Charts.php +++ b/modules/Admin/Language/gl/Charts.php @@ -37,4 +37,5 @@ return [ 'podcast_by_bandwidth' => 'Ancho de banda diario utilizado (en MB)', 'total_storage_by_month' => 'Almacenaxe mensual (en MB)', 'total_bandwidth_by_month' => 'Ancho de banda mensual utilizado (en MB)', + 'total_bandwidth_by_month_limit' => 'Limitado a {totalBandwidth} ao mes', ]; diff --git a/modules/Admin/Language/gl/Common.php b/modules/Admin/Language/gl/Common.php index 66a6f8d2..97f03466 100644 --- a/modules/Admin/Language/gl/Common.php +++ b/modules/Admin/Language/gl/Common.php @@ -40,6 +40,7 @@ return [ ], 'upload_file' => 'Subir un ficheiro', 'remote_url' => 'URL remoto', + 'save' => 'Gardar', ], 'play_episode_button' => [ 'play' => 'Reproducir', diff --git a/modules/Admin/Language/gl/Countries.php b/modules/Admin/Language/gl/Countries.php index 587c4d49..c79e1632 100644 --- a/modules/Admin/Language/gl/Countries.php +++ b/modules/Admin/Language/gl/Countries.php @@ -15,29 +15,29 @@ return [ 'AE' => 'Emiratos Árabes Unidos', 'AF' => 'Afganistán', 'AG' => 'Antigua e Barbuda', - 'AI' => 'Anguilla', + 'AI' => 'Anguila', 'AL' => 'Albania', 'AM' => 'Armenia', 'AO' => 'Angola', - 'AQ' => 'Antarctica', - 'AR' => 'Argentina', - 'AS' => 'American Samoa', + 'AQ' => 'Antártida', + 'AR' => 'Arxentina', + 'AS' => 'Samoa Americana', 'AT' => 'Austria', 'AU' => 'Australia', 'AW' => 'Aruba', - 'AX' => 'Åland Islands', - 'AZ' => 'Azerbaijan', - 'BA' => 'Bosnia and Herzegovina', + 'AX' => 'Illas Åland', + 'AZ' => 'Acerbaixán', + 'BA' => 'Bosnia e Hercegovina', 'BB' => 'Barbados', 'BD' => 'Bangladesh', - 'BE' => 'Belgium', + 'BE' => 'Bélxica', 'BF' => 'Burkina Faso', 'BG' => 'Bulgaria', 'BH' => 'Bahrain', 'BI' => 'Burundi', - 'BJ' => 'Benin', - 'BL' => 'Saint Barthélemy', - 'BM' => 'Bermuda', + 'BJ' => 'Benín', + 'BL' => 'San Bartolomé', + 'BM' => 'Bermudas', 'BN' => 'Brunei Darussalam', 'BO' => 'Bolivia, Plurinational State of', 'BQ' => 'Bonaire, Sint Eustatius and Saba', diff --git a/modules/Admin/Language/gl/Episode.php b/modules/Admin/Language/gl/Episode.php index 9e2da4b4..00919216 100644 --- a/modules/Admin/Language/gl/Episode.php +++ b/modules/Admin/Language/gl/Episode.php @@ -22,16 +22,17 @@ return [ 'all_podcast_episodes' => 'Tódolos episodios do podcast', 'back_to_podcast' => 'Volver ao podcast', 'edit' => 'Editar', + 'preview' => 'Vista previa', 'publish' => 'Publicar', 'publish_edit' => 'Editar publicación', - 'publish_date_edit' => 'Edit publication date', + 'publish_date_edit' => 'Editar data de publicación', 'unpublish' => 'Retirar publicación', 'publish_error' => 'O episodio xa está publicado.', 'publish_edit_error' => 'O episodio xa está publicado.', 'publish_cancel_error' => 'O episodio xa está publicado.', - 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', - 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', - 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'publish_date_edit_error' => 'O episodio aínda non se publicou, non podes editar a súa data de publicación.', + 'publish_date_edit_future_error' => 'A data de publicación do episodio só pode establecerse nun momento do pasado! Se queres reprogramalo, primeiro retira a súa publicación.', + 'publish_date_edit_success' => 'Actualizada correctamente a data de publicación do episodio!', 'unpublish_error' => 'O episodio non foi publicado.', 'delete' => 'Eliminar', 'go_to_page' => 'Ir á páxina', @@ -55,6 +56,7 @@ return [ }', 'episode' => 'Episodio', 'visibility' => 'Visibilidade', + 'downloads' => 'Descargas', 'comments' => 'Comentarios', 'actions' => 'Accións', ], @@ -80,12 +82,12 @@ return [ other {do multimedia} } do episodio.', 'deleteFileError' => 'Fallou a eliminación do ficheiro {type, select, - transcript {da transcrición} - chapters {dos capítulos} - image {da imaxe} - audio {do audio} - other {do multimedia} - } {file_path}. Deberías eliminala manualmente do disco.', + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + } {file_key}. Deberías eliminalo a man do disco.', 'sameSlugError' => 'Xa existe un episodio co id de url elexido.', ], 'form' => [ @@ -114,7 +116,7 @@ return [ 'bonus_hint' => 'Contido extra para o programa (por exemplo, info sobre a elaboración ou conversa casual cos participantes) ou contido promocional de outras creadoras', ], 'premium_title' => 'Premium', - 'premium' => 'Episode must be accessible to premium subscribers only', + 'premium' => 'Episodio dispoñible só para subscricións premium', 'parental_advisory' => [ 'label' => 'Aviso sobre o contido', 'hint' => 'Contén o episodio elementos explícitos?', @@ -137,9 +139,9 @@ return [ 'location_name' => 'Nome da localización e enderezo', 'location_name_hint' => 'Pode ser unha localización real ou ficticia', 'transcript' => 'Transcrición (subtítulos / texto descritivo)', - 'transcript_hint' => 'Só admite .srt', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', 'transcript_download' => 'Descargar transcrición', - 'transcript_file' => 'Fichero da transcrición (.srt)', + 'transcript_file' => 'Transcript file (.srt or .vtt)', 'transcript_remote_url' => 'URL remoto da transcrición', 'transcript_file_delete' => 'Eliminar ficheiro coa transcrición', 'chapters' => 'Capítulos', @@ -183,9 +185,9 @@ return [ 'message_warning_submit' => 'Publicar igualmente', ], 'publish_date_edit_form' => [ - 'new_publication_date' => 'New publication date', - 'new_publication_date_hint' => 'Must be set to a past date.', - 'submit' => 'Edit publication date', + 'new_publication_date' => 'Nova data de publicación', + 'new_publication_date_hint' => 'Ten que ser unha data do pasado.', + 'submit' => 'Editar data de publicación', ], 'unpublish_form' => [ 'disclaimer' => @@ -210,4 +212,14 @@ return [ 'light' => 'Claro', 'light-transparent' => 'Claro transparente', ], + 'publication_status_banner' => [ + 'draft_mode' => 'modo borrador', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Vista previa', + ], ]; diff --git a/modules/Admin/Language/gl/Navigation.php b/modules/Admin/Language/gl/Navigation.php index 68d4609d..eddbd6cb 100644 --- a/modules/Admin/Language/gl/Navigation.php +++ b/modules/Admin/Language/gl/Navigation.php @@ -9,33 +9,36 @@ declare(strict_types=1); */ return [ - 'toggle_sidebar' => 'Toggle sidebar', - 'go_to_website' => 'Go to website', - 'go_to_admin' => 'Go to admin', - 'dashboard' => 'Dashboard', - 'admin' => 'Home', + 'toggle_sidebar' => 'Activar barra lateral', + 'go_to_website' => 'Ir ao sitio web', + 'go_to_admin' => 'Ir á administración', + 'not-authorized' => 'Not authorized', + 'dashboard' => 'Taboleiro', + 'admin' => 'Inicio', 'podcasts' => 'Podcasts', - 'podcast-list' => 'All podcasts', - 'podcast-create' => 'New podcast', - 'podcast-import' => 'Import a podcast', - 'persons' => 'Persons', - 'person-list' => 'All persons', - 'person-create' => 'New person', - 'fediverse' => 'Fediverse', - 'fediverse-blocked-actors' => 'Blocked accounts', - 'fediverse-blocked-domains' => 'Blocked domains', - 'users' => 'Users', - 'user-list' => 'All users', - 'user-create' => 'New user', - 'pages' => 'Pages', - 'page-list' => 'All pages', - 'page-create' => 'New Page', - 'settings' => 'Settings', - 'settings-general' => 'General', - 'settings-theme' => 'Theme', + 'podcast-list' => 'Tódolos podcast', + 'podcast-create' => 'Novo podcast', + 'all-podcast-imports' => 'Todas as importancións de Podcast', + 'podcast-imports-add' => 'Importar un podcast', + 'persons' => 'Persoas', + 'person-list' => 'Tódalas persoas', + 'person-create' => 'Nova persoa', + 'fediverse' => 'Fediverso', + 'fediverse-blocked-actors' => 'Contas bloqueadas', + 'fediverse-blocked-domains' => 'Dominios bloqueados', + 'users' => 'Usuarias', + 'user-list' => 'Tódalas usuarias', + 'user-create' => 'Nova usuaria', + 'pages' => 'Páxinas', + 'page-list' => 'Tódalas páxinas', + 'page-create' => 'Nova páxina', + 'settings' => 'Axustes', + 'settings-general' => 'Xeral', + 'settings-theme' => 'Decorado', + 'admin-about' => 'Acerca de', 'account' => [ - 'my-account' => 'My account', - 'change-password' => 'Change password', - 'logout' => 'Logout', + 'my-account' => 'A miña conta', + 'change-password' => 'Cambiar contrasinal', + 'logout' => 'Saír', ], ]; diff --git a/modules/Admin/Language/gl/Notifications.php b/modules/Admin/Language/gl/Notifications.php index 2b139d51..b2b82a68 100644 --- a/modules/Admin/Language/gl/Notifications.php +++ b/modules/Admin/Language/gl/Notifications.php @@ -9,11 +9,11 @@ declare(strict_types=1); */ return [ - 'title' => 'Notifications', - 'reply' => '{actor_username} replied to your post', - 'favourite' => '{actor_username} favourited your post', - 'reblog' => '{actor_username} shared your post', - 'follow' => '{actor_username} started following you', - 'no_notifications' => 'No notifications', - 'mark_all_as_read' => 'Mark all as read', + 'title' => 'Notificacións', + 'reply' => '{actor_username} respondeu á túa publicación', + 'favourite' => '{actor_username} fixo favorita a túa publicación', + 'reblog' => '{actor_username} compartiu a túa publicación', + 'follow' => '{actor_username} comezou a seguirte', + 'no_notifications' => 'Sen notificacións', + 'mark_all_as_read' => 'Marcar todo como lido', ]; diff --git a/modules/Admin/Language/gl/Page.php b/modules/Admin/Language/gl/Page.php index b6f49de5..15dbde00 100644 --- a/modules/Admin/Language/gl/Page.php +++ b/modules/Admin/Language/gl/Page.php @@ -9,22 +9,22 @@ declare(strict_types=1); */ return [ - 'back_to_home' => 'Back to home', - 'page' => 'Page', - 'all_pages' => 'All pages', - 'create' => 'New page', - 'go_to_page' => 'Go to page', - 'edit' => 'Edit page', - 'delete' => 'Delete page', + 'back_to_home' => 'Volver ao inicio', + 'page' => 'Páxina', + 'all_pages' => 'Tódalas páxinas', + 'create' => 'Nova páxina', + 'go_to_page' => 'Ir á páxina', + 'edit' => 'Editar páxina', + 'delete' => 'Eliminar páxina', 'form' => [ - 'title' => 'Title', - 'permalink' => 'Permalink', - 'content' => 'Content', - 'submit_create' => 'Create page', - 'submit_edit' => 'Save', + 'title' => 'Título', + 'permalink' => 'Ligazón permanente', + 'content' => 'Contido', + 'submit_create' => 'Crear páxina', + 'submit_edit' => 'Gardar', ], 'messages' => [ - 'createSuccess' => 'The page “{pageTitle}” was created successfully!', - 'editSuccess' => 'The page was successfully updated!', + 'createSuccess' => 'Creouse correctamente a páxina "{pageTitle}"!', + 'editSuccess' => 'Actualizouse correctamente a páxina!', ], ]; diff --git a/modules/Admin/Language/gl/Pager.php b/modules/Admin/Language/gl/Pager.php index e25ee638..390b2e72 100644 --- a/modules/Admin/Language/gl/Pager.php +++ b/modules/Admin/Language/gl/Pager.php @@ -9,13 +9,13 @@ declare(strict_types=1); */ return [ - 'pageNavigation' => 'Page navigation', - 'first' => 'First', - 'previous' => 'Previous', - 'next' => 'Next', - 'last' => 'Last', - 'older' => 'Older', - 'newer' => 'Newer', - 'invalidTemplate' => '{0} is not a valid Pager template.', - 'invalidPaginationGroup' => '{0} is not a valid Pagination group.', + 'pageNavigation' => 'Navegador de páxinas', + 'first' => 'Primeira', + 'previous' => 'Anterior', + 'next' => 'Seguinte', + 'last' => 'Última', + 'older' => 'Máis antiga', + 'newer' => 'Máis nova', + 'invalidTemplate' => '{0} non é un modelo para páxinas válido.', + 'invalidPaginationGroup' => '{0} non é un grupo de navegación de páxinas válido.', ]; diff --git a/modules/Admin/Language/gl/Person.php b/modules/Admin/Language/gl/Person.php index a652be9f..b8d5bd5d 100644 --- a/modules/Admin/Language/gl/Person.php +++ b/modules/Admin/Language/gl/Person.php @@ -9,57 +9,57 @@ declare(strict_types=1); */ return [ - 'persons' => 'Persons', - 'all_persons' => 'All persons', - 'no_person' => 'Nobody found!', - 'create' => 'Create a person', - 'view' => 'View person', - 'edit' => 'Edit person', - 'delete' => 'Delete person', + 'persons' => 'Persoas', + 'all_persons' => 'Tódalas persoas', + 'no_person' => 'Non hai ninguén!', + 'create' => 'Crear unha persoa', + 'view' => 'Ver persoa', + 'edit' => 'Editar persoa', + 'delete' => 'Eliminar persoa', 'messages' => [ - 'createSuccess' => 'Person has been successfully created!', - 'editSuccess' => 'Person has been successfully updated!', - 'deleteSuccess' => 'Person has been removed!', + 'createSuccess' => 'Persoa creada correctamente!', + 'editSuccess' => 'Persoa actualizada correctamente!', + 'deleteSuccess' => 'Eliminouse a Persoa!', ], 'form' => [ - 'avatar' => 'Avatar', + 'avatar' => 'Imaxe de perfil', 'avatar_size_hint' => - 'Avatar must be squared and at least 400px wide and tall.', - 'full_name' => 'Full name', - 'full_name_hint' => 'This is the full name or alias of the person.', - 'unique_name' => 'Unique name', - 'unique_name_hint' => 'Used for URLs', - 'information_url' => 'Information URL', + 'A imaxe do perfil ten que ser cadrada e como pouco de 400px de alto e ancho.', + 'full_name' => 'Nome completo', + 'full_name_hint' => 'Este é o nome completo ou alias da persoa.', + 'unique_name' => 'Nome único', + 'unique_name_hint' => 'Usado nos URLs', + 'information_url' => 'URL de información', 'information_url_hint' => - 'Url to a relevant resource of information about the person, such as a homepage or third-party profile platform.', - 'submit_create' => 'Create person', - 'submit_edit' => 'Save person', + 'Url a un recurso con información relevante sobre a persoa, como unha web ou plataforma de terceiras partes.', + 'submit_create' => 'Crear persoa', + 'submit_edit' => 'Gardar persoa', ], 'podcast_form' => [ - 'title' => 'Manage persons', - 'add_section_title' => 'Add persons to this podcast', - 'add_section_subtitle' => 'You may pick several persons and roles.', - 'persons' => 'Persons', + 'title' => 'Xestionar persoas', + 'add_section_title' => 'Engadir persoas a este podcast', + 'add_section_subtitle' => 'Podes elexir varias persoas e roles.', + 'persons' => 'Persoas', 'persons_hint' => - 'You may select one or several persons with the same roles. You need to create the persons first.', + 'Podes elexir unha ou varias persoas cos mesmos roles. Precisas crear primeiro as persoas.', 'roles' => 'Roles', 'roles_hint' => - 'You may select none, one or several roles for a person.', - 'submit_add' => 'Add person(s)', - 'remove' => 'Remove', + 'Podes seleccionar un, ningún ou varios roles para a persoa.', + 'submit_add' => 'Engadir persoa(s)', + 'remove' => 'Eliminar', ], 'episode_form' => [ - 'title' => 'Manage persons', - 'add_section_title' => 'Add persons to this episode', - 'add_section_subtitle' => 'You may pick several persons and roles.', - 'persons' => 'Persons', + 'title' => 'Xestionar persoas', + 'add_section_title' => 'Engdir persoas a este episodio', + 'add_section_subtitle' => 'Podes elexir varias persoas e roles.', + 'persons' => 'Persoas', 'persons_hint' => - 'You may select one or several persons with the same roles. You need to create the persons first.', + 'Podes elexir unha ou varias persoas cos mesmos roles. Primeiro tes que crear as persoas.', 'roles' => 'Roles', 'roles_hint' => - 'You may select none, one or several roles for a person.', - 'submit_add' => 'Add person(s)', - 'remove' => 'Remove', + 'Podes seleccionar un, ningún ou varios roles para unha persoa.', + 'submit_add' => 'Engadir persoa(s)', + 'remove' => 'Eliminar', ], - 'credits' => 'Credits', + 'credits' => 'Créditos', ]; diff --git a/modules/Admin/Language/gl/Platforms.php b/modules/Admin/Language/gl/Platforms.php index ab17d599..9d34da3a 100644 --- a/modules/Admin/Language/gl/Platforms.php +++ b/modules/Admin/Language/gl/Platforms.php @@ -9,22 +9,35 @@ declare(strict_types=1); */ return [ - 'title' => 'Platforms', - 'home_url' => 'Go to {platformName} website', - 'submit_url' => 'Submit your podcast on {platformName}', - 'visible' => 'Display in podcast homepage?', - 'on_embed' => 'Display on embeddable player?', - 'remove' => 'Remove {platformName}', - 'submit' => 'Save', + 'title' => [ + 'podcasting' => 'Plataformas de podcast', + 'social' => 'Redes sociais', + 'funding' => 'Finanzamento', + ], + 'website' => 'Sitio web', + 'home_url' => 'Ir á web de {platformName}', + 'register' => 'Crear conta', + 'submit_url' => 'Envía o teu podcast en {platformName}', + 'your_link' => 'A túa ligazón', + 'your_id' => [ + 'podcasting' => 'O teu ID', + 'social' => 'O teu ID', + 'funding' => 'O teu CTA', + ], + 'your_cta' => 'Chamar á acción', + 'visible' => 'Mostrar na páxina de inicio do podcast?', + 'on_embed' => 'Mostrar no navegador incrustable?', + 'remove' => 'Eliminar {platformName}', + 'submit' => 'Gardar', 'messages' => [ - 'updateSuccess' => 'Platform links have been successfully updated!', - 'removeLinkSuccess' => 'The platform link has been removed.', + 'updateSuccess' => 'Actualizáronse correctamente as ligazóns á plataforma!', + 'removeLinkSuccess' => 'Eliminouse a ligazón á plataforma.', 'removeLinkError' => - 'The platform link could not be removed. Try again.', + 'A ligazón á plataforma non se puido eliminar. Inténtao outra vez.', ], 'description' => [ - 'podcasting' => 'The podcast ID on this platform', - 'social' => 'The podcast account ID on this platform', - 'funding' => 'Call to action message', + 'podcasting' => 'ID do podcast nesta plataforma', + 'social' => 'ID da conta do podcast nesta plataforma', + 'funding' => 'Mensaxe de convite a realizar acción', ], ]; diff --git a/modules/Admin/Language/gl/Podcast.php b/modules/Admin/Language/gl/Podcast.php index 80e9d971..baa05597 100644 --- a/modules/Admin/Language/gl/Podcast.php +++ b/modules/Admin/Language/gl/Podcast.php @@ -13,6 +13,7 @@ return [ 'no_podcast' => 'Non se atopan podcast!', 'create' => 'Crear un podcast', 'import' => 'Importar podcast', + 'all_imports' => 'Importacións de podcasts', 'new_episode' => 'Novo Episodio', 'view' => 'Ver podcast', 'edit' => 'Editar podcast', @@ -21,6 +22,7 @@ return [ 'delete' => 'Eliminar podcast', 'see_episodes' => 'Ver episodios', 'see_contributors' => 'Ver colaboradoras', + 'monetization_other' => 'Outros xeitos', 'go_to_page' => 'Ir á páxina', 'latest_episodes' => 'Últimos episodios', 'see_all_episodes' => 'Ver tódolos episodios', @@ -48,68 +50,86 @@ return [ other {# episodios foron engadidos} } ao podcast!', 'podcastFeedUpToDate' => 'O podcast xa está ao día.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', - 'publishError' => 'This podcast is either already published or scheduled for publication.', - 'publishEditError' => 'This podcast is not scheduled for publication.', - 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', - 'scheduleDateError' => 'Schedule date must be set!', + 'publishError' => 'Este podcast ou ben xa foi publicado ou está programada a súa publicación.', + 'publishEditError' => 'Este podcast non ten a publicación programada.', + 'publishCancelSuccess' => 'Cancelouse correctamente a publicación do podcast!', + 'scheduleDateError' => 'Hai que establecer a data da publicación!', ], 'form' => [ - 'identity_section_title' => 'Podcast identity', - 'identity_section_subtitle' => 'These fields allow you to get noticed.', - 'cover' => 'Podcast cover', - 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', - 'banner' => 'Podcast banner', - 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', - 'banner_delete' => 'Delete podcast banner', - 'title' => 'Title', - 'handle' => 'Handle', + 'identity_section_title' => 'Identidade do podcast', + 'identity_section_subtitle' => 'Estes campos permítenche recibir notificacións.', + 'fediverse_section_title' => 'Identidade no fediverso', + + 'cover' => 'Portada do podcast', + 'cover_size_hint' => 'A portada ten que ser cadrada e como mínimo de 1400px de alto e ancho.', + 'banner' => 'Cabeceira do podcast', + 'banner_size_hint' => 'A imaxe de cabeceira debe ter proporción 3:1 e 1500px. como mínimo.', + 'banner_delete' => 'Eliminar cabeceira do podcast', + 'title' => 'Título', + 'handle' => 'Identificador', 'handle_hint' => - 'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.', + 'Utilizado para identificar o podcast. Permítense maiúsculas, minúsculas, números e trazo baixo.', 'type' => [ - 'label' => 'Type', - 'episodic' => 'Episodic', - 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', - 'serial' => 'Serial', - 'serial_hint' => 'If episodes are intended to be consumed in sequential order. The oldest episodes will be presented first.', + 'label' => 'Tipo', + 'episodic' => 'Recurrente', + 'episodic_hint' => 'Se os episodios non teñen unha orde predeterminada para ser escoitados. Os novos episodios serán mostrados antes.', + 'serial' => 'Serie', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', ], - 'description' => 'Description', - 'classification_section_title' => 'Classification', + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + ], + 'description' => 'Descrición', + 'classification_section_title' => 'Clasificación', 'classification_section_subtitle' => - 'These fields will impact your audience and competition.', - 'language' => 'Language', - 'category' => 'Category', - 'category_placeholder' => 'Select a category…', - 'other_categories' => 'Other categories', + 'Estos campos terán impacto na túa audiencia e competencia.', + 'language' => 'Idioma', + 'category' => 'Categoría', + 'category_placeholder' => 'Elixe unha categoría…', + 'other_categories' => 'Outras categorías', 'parental_advisory' => [ - 'label' => 'Parental advisory', - 'hint' => 'Does it contain explicit content?', - 'undefined' => 'undefined', - 'clean' => 'Clean', - 'explicit' => 'Explicit', + 'label' => 'Aviso parental', + 'hint' => 'Inclúe contidos explícitos?', + 'undefined' => 'sen definir', + 'clean' => 'Aceptable', + 'explicit' => 'Explícito', ], - 'author_section_title' => 'Author', - 'author_section_subtitle' => 'Who is managing the podcast?', - 'owner_name' => 'Owner name', + 'author_section_title' => 'Autoría', + 'author_section_subtitle' => 'Quen xestiona o podcast?', + 'owner_name' => 'Nome da propietaria', 'owner_name_hint' => - 'For administrative use only. Visible in the public RSS feed.', - 'owner_email' => 'Owner email', + 'Só para uso administrativo. Visible na fonte RSS pública.', + 'owner_email' => 'Correo da propietaria', 'owner_email_hint' => - 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', - 'publisher' => 'Publisher', + 'Será utilizado pola maioría das plataformas para verificar a propiedade do podcast. Visible na fonte RSS pública.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', + 'publisher' => 'Editorial', 'publisher_hint' => - 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', - 'copyright' => 'Copyright', - 'location_section_title' => 'Location', - 'location_section_subtitle' => 'What place is this podcast about?', - 'location_name' => 'Location name or address', - 'location_name_hint' => 'This can be a real place or fictional', - 'monetization_section_title' => 'Monetization', + 'O grupo responsable da creación do programa. Normalmente refírese á empresa nai ou rede do podcast. O campo a veces etiquétase como \'Autor\'.', + 'copyright' => 'Dereitos', + 'location_section_title' => 'Localización', + 'location_section_subtitle' => 'De qué lugar trata o podcast?', + 'location_name' => 'Nome do lugar ou enderezo', + 'location_name_hint' => 'Pode ser un lugar real ou ficticio', + 'monetization_section_title' => 'Monetización', 'monetization_section_subtitle' => - 'Earn money thanks to your audience.', + 'Obter cartos grazas á túa audiencia.', 'premium' => 'Premium', - 'premium_by_default' => 'Episodes must be set as premium by default', - 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'premium_by_default' => 'Os episodios estableceranse por defecto como premium', + 'premium_by_default_hint' => 'Os episodios vanse marcar por defecto como premium. Podes igualmente elexir algúns episodios, mostras ou extras como públicos.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', 'payment_pointer' => 'Payment Pointer for Web Monetization', 'payment_pointer_hint' => 'This is your where you will receive money thanks to Web Monetization', @@ -118,11 +138,12 @@ return [ 'If you need RSS tags that Castopod does not handle, set them here.', 'custom_rss' => 'Custom RSS tags for the podcast', 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', 'new_feed_url' => 'New feed URL', 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', 'partnership' => 'Partnership', 'partner_id' => 'ID', 'partner_link_url' => 'Link URL', @@ -130,7 +151,6 @@ return [ 'partner_id_hint' => 'Your own partner ID', 'partner_link_url_hint' => 'The generic partner link address', 'partner_image_url_hint' => 'The generic partner image address', - 'status_section_title' => 'Status', 'block' => 'Podcast should be hidden from public catalogues', 'block_hint' => 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', diff --git a/modules/Admin/Language/gl/PodcastImport.php b/modules/Admin/Language/gl/PodcastImport.php deleted file mode 100644 index a5200334..00000000 --- a/modules/Admin/Language/gl/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'The podcast to import', - 'old_podcast_section_subtitle' => - 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', - 'imported_feed_url' => 'Feed URL', - 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', - 'new_podcast_section_title' => 'The new podcast', - 'advanced_params_section_title' => 'Advanced parameters', - 'advanced_params_section_subtitle' => - 'Keep the default values if you have no idea of what the fields are for.', - 'slug_field' => 'Campo a utilizar para crear o id de url para o episodio', - 'description_field' => - 'Source field used for episode description / show notes', - 'force_renumber' => 'Force episodes renumbering', - 'force_renumber_hint' => - 'Use this if your podcast does not have episode numbers but wish to set them during import.', - 'season_number' => 'Season number', - 'season_number_hint' => - 'Use this if your podcast does not have a season number but wish to set one during import. Leave blank otherwise.', - 'max_episodes' => 'Maximum number of episodes to import', - 'max_episodes_hint' => 'Leave blank to import all episodes', - 'lock_import' => - 'This feed is protected. You cannot import it. If you are the owner, unprotect it on the origin platform.', - 'submit' => 'Import podcast', -]; diff --git a/modules/Admin/Language/gl/PodcastNavigation.php b/modules/Admin/Language/gl/PodcastNavigation.php index b4d7ddc0..c47b51dc 100644 --- a/modules/Admin/Language/gl/PodcastNavigation.php +++ b/modules/Admin/Language/gl/PodcastNavigation.php @@ -9,30 +9,34 @@ declare(strict_types=1); */ return [ - 'go_to_page' => 'Go to podcast page', - 'dashboard' => 'Podcast dashboard', - 'podcast-view' => 'Home', - 'podcast-edit' => 'Edit podcast', - 'podcast-persons-manage' => 'Manage persons', - 'episodes' => 'Episodes', - 'episode-list' => 'All episodes', - 'episode-create' => 'New episode', - 'analytics' => 'Analytics', - 'podcast-analytics' => 'Audience overview', - 'podcast-analytics-webpages' => 'Web pages visits', - 'podcast-analytics-locations' => 'Locations', - 'podcast-analytics-unique-listeners' => 'Unique listeners', - 'podcast-analytics-players' => 'Players', - 'podcast-analytics-listening-time' => 'Listening time', - 'podcast-analytics-time-periods' => 'Time periods', - 'premium' => 'Premium', - 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', - 'contributors' => 'Contributors', - 'contributor-list' => 'All contributors', - 'contributor-add' => 'Add contributor', - 'platforms' => 'External platforms', - 'platforms-podcasting' => 'Podcasting', - 'platforms-social' => 'Social networks', - 'platforms-funding' => 'Funding', + 'go_to_page' => 'Ir á páxina do podcast', + 'rss_feed' => 'RSS feed', + 'dashboard' => 'Taboleiro do podcast', + 'podcast-view' => 'Inicio', + 'podcast-edit' => 'Editar podcast', + 'podcast-persons-manage' => 'Xestionar persoas', + 'podcast-imports' => 'Importacións de podcasts', + 'podcast-imports-sync' => 'Sync feeds', + 'episodes' => 'Episodios', + 'episode-list' => 'Todos os episodios', + 'episode-create' => 'Novo episodio', + 'analytics' => 'Análise', + 'podcast-analytics' => 'Ollada sobre a audiencia', + 'podcast-analytics-webpages' => 'Visitas á páxina web', + 'podcast-analytics-locations' => 'Localizacións', + 'podcast-analytics-unique-listeners' => 'Oíntes únicos', + 'podcast-analytics-players' => 'Reprodutores', + 'podcast-analytics-listening-time' => 'Tempo de escoita', + 'podcast-analytics-time-periods' => 'Período de tempo', + 'monetization' => 'Monetization', + 'subscription-list' => 'Todas as subscricións', + 'subscription-create' => 'Add subscription', + 'contributors' => 'Contribúen', + 'contributor-list' => 'Todas as contribucións', + 'contributor-add' => 'Engadir colaboración', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', + 'platforms-social' => 'Redes sociais', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', ]; diff --git a/modules/Admin/Language/gl/Validation.php b/modules/Admin/Language/gl/Validation.php index 54aaef52..42d6a672 100644 --- a/modules/Admin/Language/gl/Validation.php +++ b/modules/Admin/Language/gl/Validation.php @@ -13,6 +13,5 @@ return [ 'ou ben {field} non é unha imaxe ou non é suficientemente alta ou ancha.', 'is_image_ratio' => 'ou ben {field} non é unha imaxe ou non ten proporcións axeitadas.', - 'validate_url' => - 'O campo {field} ten que ser un URL válido (ex. https://exemplo.com/).', + 'is_json' => '{field} contains invalid JSON.', ]; diff --git a/modules/Admin/Language/id/AboutCastopod.php b/modules/Admin/Language/id/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/id/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/id/Breadcrumb.php b/modules/Admin/Language/id/Breadcrumb.php index 676e47b7..4e6f3a8e 100644 --- a/modules/Admin/Language/id/Breadcrumb.php +++ b/modules/Admin/Language/id/Breadcrumb.php @@ -19,6 +19,7 @@ return [ 'pages' => 'pages', 'settings' => 'settings', 'theme' => 'theme', + 'about' => 'about', 'add' => 'add', 'new' => 'new', 'edit' => 'edit', @@ -28,15 +29,19 @@ return [ 'publish-date-edit' => 'edit publication date', 'unpublish' => 'unpublish', 'delete' => 'delete', + 'remove' => 'remove', 'fediverse' => 'fediverse', - 'block-lists' => 'block lists', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', 'users' => 'pengguna', 'my-account' => 'akun saya', 'change-password' => 'ubah kata sandi', - 'import' => 'feed import', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', 'platforms' => 'platforms', 'social' => 'social networks', 'funding' => 'funding', + 'monetization-other' => 'other monetization', 'analytics' => 'analitik', 'locations' => 'locations', 'webpages' => 'web pages', diff --git a/modules/Admin/Language/id/Charts.php b/modules/Admin/Language/id/Charts.php index 4b33530e..6ede2510 100644 --- a/modules/Admin/Language/id/Charts.php +++ b/modules/Admin/Language/id/Charts.php @@ -37,4 +37,5 @@ return [ 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', 'total_storage_by_month' => 'Monthly storage (in MB)', 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', ]; diff --git a/modules/Admin/Language/id/Common.php b/modules/Admin/Language/id/Common.php index 596c8bcd..74addcf2 100644 --- a/modules/Admin/Language/id/Common.php +++ b/modules/Admin/Language/id/Common.php @@ -40,6 +40,7 @@ return [ ], 'upload_file' => 'Upload a file', 'remote_url' => 'Remote URL', + 'save' => 'Save', ], 'play_episode_button' => [ 'play' => 'Play', diff --git a/modules/Admin/Language/id/Episode.php b/modules/Admin/Language/id/Episode.php index 91313a7c..4fa846e3 100644 --- a/modules/Admin/Language/id/Episode.php +++ b/modules/Admin/Language/id/Episode.php @@ -22,6 +22,7 @@ return [ 'all_podcast_episodes' => 'All podcast episodes', 'back_to_podcast' => 'Go back to podcast', 'edit' => 'Edit', + 'preview' => 'Preview', 'publish' => 'Publish', 'publish_edit' => 'Edit publication', 'publish_date_edit' => 'Edit publication date', @@ -55,6 +56,7 @@ return [ }', 'episode' => 'Episode', 'visibility' => 'Visibility', + 'downloads' => 'Downloads', 'comments' => 'Comments', 'actions' => 'Actions', ], @@ -85,7 +87,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ @@ -137,9 +139,9 @@ return [ 'location_name' => 'Location name or address', 'location_name_hint' => 'This can be a real or fictional location', 'transcript' => 'Transcript (subtitles / closed captions)', - 'transcript_hint' => 'Only .srt are allowed.', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', 'transcript_download' => 'Download transcript', - 'transcript_file' => 'Transcript file (.srt)', + 'transcript_file' => 'Transcript file (.srt or .vtt)', 'transcript_remote_url' => 'Remote url for transcript', 'transcript_file_delete' => 'Delete transcript file', 'chapters' => 'Chapters', @@ -210,4 +212,14 @@ return [ 'light' => 'Light', 'light-transparent' => 'Light transparent', ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], ]; diff --git a/modules/Admin/Language/id/Navigation.php b/modules/Admin/Language/id/Navigation.php index 68d4609d..f3ffb129 100644 --- a/modules/Admin/Language/id/Navigation.php +++ b/modules/Admin/Language/id/Navigation.php @@ -12,12 +12,14 @@ return [ 'toggle_sidebar' => 'Toggle sidebar', 'go_to_website' => 'Go to website', 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', 'dashboard' => 'Dashboard', 'admin' => 'Home', 'podcasts' => 'Podcasts', 'podcast-list' => 'All podcasts', 'podcast-create' => 'New podcast', - 'podcast-import' => 'Import a podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', 'persons' => 'Persons', 'person-list' => 'All persons', 'person-create' => 'New person', @@ -33,6 +35,7 @@ return [ 'settings' => 'Settings', 'settings-general' => 'General', 'settings-theme' => 'Theme', + 'admin-about' => 'About', 'account' => [ 'my-account' => 'My account', 'change-password' => 'Change password', diff --git a/modules/Admin/Language/id/Platforms.php b/modules/Admin/Language/id/Platforms.php index ab17d599..e161181c 100644 --- a/modules/Admin/Language/id/Platforms.php +++ b/modules/Admin/Language/id/Platforms.php @@ -9,9 +9,22 @@ declare(strict_types=1); */ return [ - 'title' => 'Platforms', + 'title' => [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', 'visible' => 'Display in podcast homepage?', 'on_embed' => 'Display on embeddable player?', 'remove' => 'Remove {platformName}', diff --git a/modules/Admin/Language/id/Podcast.php b/modules/Admin/Language/id/Podcast.php index 426b763b..ff0daebc 100644 --- a/modules/Admin/Language/id/Podcast.php +++ b/modules/Admin/Language/id/Podcast.php @@ -13,6 +13,7 @@ return [ 'no_podcast' => 'No podcast found!', 'create' => 'Create podcast', 'import' => 'Import podcast', + 'all_imports' => 'Podcast imports', 'new_episode' => 'New Episode', 'view' => 'View podcast', 'edit' => 'Edit podcast', @@ -21,6 +22,7 @@ return [ 'delete' => 'Delete podcast', 'see_episodes' => 'See episodes', 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', 'go_to_page' => 'Go to page', 'latest_episodes' => 'Latest episodes', 'see_all_episodes' => 'See all episodes', @@ -48,7 +50,6 @@ return [ other {# episodes were} } added to the podcast!', 'podcastFeedUpToDate' => 'Podcast is already up to date.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', 'publishError' => 'This podcast is either already published or scheduled for publication.', 'publishEditError' => 'This podcast is not scheduled for publication.', 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', @@ -57,6 +58,8 @@ return [ 'form' => [ 'identity_section_title' => 'Podcast identity', 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + 'cover' => 'Podcast cover', 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', 'banner' => 'Podcast banner', @@ -71,7 +74,17 @@ return [ 'episodic' => 'Episodic', 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', 'serial' => 'Serial', - 'serial_hint' => 'If episodes are intended to be consumed in sequential order. The oldest episodes will be presented first.', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', ], 'description' => 'Description', 'classification_section_title' => 'Classification', @@ -96,6 +109,8 @@ return [ 'owner_email' => 'Owner email', 'owner_email_hint' => 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', 'publisher' => 'Publisher', 'publisher_hint' => 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', @@ -110,6 +125,11 @@ return [ 'premium' => 'Premium', 'premium_by_default' => 'Episodes must be set as premium by default', 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', 'payment_pointer' => 'Payment Pointer for Web Monetization', 'payment_pointer_hint' => 'This is your where you will receive money thanks to Web Monetization', @@ -118,11 +138,12 @@ return [ 'If you need RSS tags that Castopod does not handle, set them here.', 'custom_rss' => 'Custom RSS tags for the podcast', 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', 'new_feed_url' => 'New feed URL', 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', 'partnership' => 'Partnership', 'partner_id' => 'ID', 'partner_link_url' => 'Link URL', @@ -130,7 +151,6 @@ return [ 'partner_id_hint' => 'Your own partner ID', 'partner_link_url_hint' => 'The generic partner link address', 'partner_image_url_hint' => 'The generic partner image address', - 'status_section_title' => 'Status', 'block' => 'Podcast should be hidden from public catalogues', 'block_hint' => 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', diff --git a/modules/Admin/Language/id/PodcastImport.php b/modules/Admin/Language/id/PodcastImport.php deleted file mode 100644 index 7c3ef67d..00000000 --- a/modules/Admin/Language/id/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'The podcast to import', - 'old_podcast_section_subtitle' => - 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', - 'imported_feed_url' => 'Feed URL', - 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', - 'new_podcast_section_title' => 'The new podcast', - 'advanced_params_section_title' => 'Advanced parameters', - 'advanced_params_section_subtitle' => - 'Keep the default values if you have no idea of what the fields are for.', - 'slug_field' => 'Field to be used to calculate episode slug', - 'description_field' => - 'Source field used for episode description / show notes', - 'force_renumber' => 'Force episodes renumbering', - 'force_renumber_hint' => - 'Use this if your podcast does not have episode numbers but wish to set them during import.', - 'season_number' => 'Season number', - 'season_number_hint' => - 'Use this if your podcast does not have a season number but wish to set one during import. Leave blank otherwise.', - 'max_episodes' => 'Maximum number of episodes to import', - 'max_episodes_hint' => 'Leave blank to import all episodes', - 'lock_import' => - 'This feed is protected. You cannot import it. If you are the owner, unprotect it on the origin platform.', - 'submit' => 'Import podcast', -]; diff --git a/modules/Admin/Language/id/PodcastNavigation.php b/modules/Admin/Language/id/PodcastNavigation.php index b4d7ddc0..bb777707 100644 --- a/modules/Admin/Language/id/PodcastNavigation.php +++ b/modules/Admin/Language/id/PodcastNavigation.php @@ -10,10 +10,13 @@ declare(strict_types=1); return [ 'go_to_page' => 'Go to podcast page', + 'rss_feed' => 'RSS feed', 'dashboard' => 'Podcast dashboard', 'podcast-view' => 'Home', 'podcast-edit' => 'Edit podcast', 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', 'episodes' => 'Episodes', 'episode-list' => 'All episodes', 'episode-create' => 'New episode', @@ -25,14 +28,15 @@ return [ 'podcast-analytics-players' => 'Players', 'podcast-analytics-listening-time' => 'Listening time', 'podcast-analytics-time-periods' => 'Time periods', - 'premium' => 'Premium', + 'monetization' => 'Monetization', 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', + 'subscription-create' => 'Add subscription', 'contributors' => 'Contributors', 'contributor-list' => 'All contributors', 'contributor-add' => 'Add contributor', - 'platforms' => 'External platforms', - 'platforms-podcasting' => 'Podcasting', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', 'platforms-social' => 'Social networks', - 'platforms-funding' => 'Funding', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', ]; diff --git a/modules/Admin/Language/id/User.php b/modules/Admin/Language/id/User.php deleted file mode 100644 index 585d6799..00000000 --- a/modules/Admin/Language/id/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', - 'ban' => 'Ban', - 'unban' => 'Unban', - 'delete' => 'Delete', - 'create' => 'New user', - 'view' => "{username}'s info", - 'all_users' => 'All users', - 'list' => [ - 'user' => 'User', - 'roles' => 'Roles', - 'banned' => 'Banned?', - ], - 'form' => [ - 'email' => 'Email', - 'username' => 'Username', - 'password' => 'Password', - 'new_password' => 'New Password', - 'roles' => 'Roles', - 'permissions' => 'Permissions', - 'submit_create' => 'Create user', - 'submit_edit' => 'Save', - 'submit_password_change' => 'Change!', - ], - 'roles' => [ - 'superadmin' => 'Super admin', - ], - 'messages' => [ - 'createSuccess' => - 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => - "{username}'s roles have been successfully updated.", - 'forcePassResetSuccess' => - '{username} will be prompted with a password reset upon next visit.', - 'banSuccess' => '{username} has been banned.', - 'unbanSuccess' => '{username} has been unbanned.', - 'editOwnerError' => - '{username} is the instance owner, you cannot edit its roles.', - 'banSuperAdminError' => - '{username} is a superadmin, one does not simply ban a superadmin…', - 'deleteSuperAdminError' => - '{username} is a superadmin, one does not simply delete a superadmin…', - 'deleteSuccess' => '{username} has been deleted.', - ], -]; diff --git a/modules/Admin/Language/id/Validation.php b/modules/Admin/Language/id/Validation.php index 750b1968..f76c3163 100644 --- a/modules/Admin/Language/id/Validation.php +++ b/modules/Admin/Language/id/Validation.php @@ -13,6 +13,5 @@ return [ '{field} is either not an image, or it is not wide or tall enough.', 'is_image_ratio' => '{field} is either not an image or not of the right ratio.', - 'validate_url' => - 'The {field} field must be a valid URL (eg. https://example.com/).', + 'is_json' => '{field} contains invalid JSON.', ]; diff --git a/modules/Admin/Language/it/AboutCastopod.php b/modules/Admin/Language/it/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/it/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/it/Breadcrumb.php b/modules/Admin/Language/it/Breadcrumb.php index 9876ee9b..7c492350 100644 --- a/modules/Admin/Language/it/Breadcrumb.php +++ b/modules/Admin/Language/it/Breadcrumb.php @@ -19,6 +19,7 @@ return [ 'pages' => 'pagine', 'settings' => 'impostazioni', 'theme' => 'tema', + 'about' => 'about', 'add' => 'aggiungi', 'new' => 'nuovo', 'edit' => 'modifica', @@ -28,15 +29,19 @@ return [ 'publish-date-edit' => 'edit publication date', 'unpublish' => 'annulla pubblicazione', 'delete' => 'elimina', + 'remove' => 'remove', 'fediverse' => 'fediverso', - 'block-lists' => 'elenco bloccati', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', 'users' => 'utenti', 'my-account' => 'il mio profilo', 'change-password' => 'cambia la password', - 'import' => 'importazione feed', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', 'platforms' => 'piattaforme', 'social' => 'social networks', 'funding' => 'finanziamento', + 'monetization-other' => 'other monetization', 'analytics' => 'analytics', 'locations' => 'posizioni', 'webpages' => 'pagine web', diff --git a/modules/Admin/Language/it/Charts.php b/modules/Admin/Language/it/Charts.php index 383d7bf8..88455fc2 100644 --- a/modules/Admin/Language/it/Charts.php +++ b/modules/Admin/Language/it/Charts.php @@ -37,4 +37,5 @@ return [ 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', 'total_storage_by_month' => 'Monthly storage (in MB)', 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', ]; diff --git a/modules/Admin/Language/it/Common.php b/modules/Admin/Language/it/Common.php index bff79dff..82ad7a8c 100644 --- a/modules/Admin/Language/it/Common.php +++ b/modules/Admin/Language/it/Common.php @@ -40,6 +40,7 @@ return [ ], 'upload_file' => 'Carica un file', 'remote_url' => 'URL remoto', + 'save' => 'Save', ], 'play_episode_button' => [ 'play' => 'Riproduci', diff --git a/modules/Admin/Language/it/Episode.php b/modules/Admin/Language/it/Episode.php index 6cb572a3..a583463a 100644 --- a/modules/Admin/Language/it/Episode.php +++ b/modules/Admin/Language/it/Episode.php @@ -22,6 +22,7 @@ return [ 'all_podcast_episodes' => 'Tutti gli episodi del podcast', 'back_to_podcast' => 'Torna a podcast', 'edit' => 'Modifica', + 'preview' => 'Preview', 'publish' => 'Pubblica', 'publish_edit' => 'Modifica pubblicazione', 'publish_date_edit' => 'Edit publication date', @@ -55,6 +56,7 @@ return [ }', 'episode' => 'Episode', 'visibility' => 'Visibility', + 'downloads' => 'Downloads', 'comments' => 'Comments', 'actions' => 'Actions', ], @@ -85,7 +87,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ @@ -137,9 +139,9 @@ return [ 'location_name' => 'Location name or address', 'location_name_hint' => 'This can be a real or fictional location', 'transcript' => 'Transcript (subtitles / closed captions)', - 'transcript_hint' => 'Only .srt are allowed.', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', 'transcript_download' => 'Download transcript', - 'transcript_file' => 'Transcript file (.srt)', + 'transcript_file' => 'Transcript file (.srt or .vtt)', 'transcript_remote_url' => 'Remote url for transcript', 'transcript_file_delete' => 'Delete transcript file', 'chapters' => 'Chapters', @@ -210,4 +212,14 @@ return [ 'light' => 'Light', 'light-transparent' => 'Light transparent', ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], ]; diff --git a/modules/Admin/Language/it/Navigation.php b/modules/Admin/Language/it/Navigation.php index 68d4609d..f3ffb129 100644 --- a/modules/Admin/Language/it/Navigation.php +++ b/modules/Admin/Language/it/Navigation.php @@ -12,12 +12,14 @@ return [ 'toggle_sidebar' => 'Toggle sidebar', 'go_to_website' => 'Go to website', 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', 'dashboard' => 'Dashboard', 'admin' => 'Home', 'podcasts' => 'Podcasts', 'podcast-list' => 'All podcasts', 'podcast-create' => 'New podcast', - 'podcast-import' => 'Import a podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', 'persons' => 'Persons', 'person-list' => 'All persons', 'person-create' => 'New person', @@ -33,6 +35,7 @@ return [ 'settings' => 'Settings', 'settings-general' => 'General', 'settings-theme' => 'Theme', + 'admin-about' => 'About', 'account' => [ 'my-account' => 'My account', 'change-password' => 'Change password', diff --git a/modules/Admin/Language/it/Platforms.php b/modules/Admin/Language/it/Platforms.php index ab17d599..e161181c 100644 --- a/modules/Admin/Language/it/Platforms.php +++ b/modules/Admin/Language/it/Platforms.php @@ -9,9 +9,22 @@ declare(strict_types=1); */ return [ - 'title' => 'Platforms', + 'title' => [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', 'visible' => 'Display in podcast homepage?', 'on_embed' => 'Display on embeddable player?', 'remove' => 'Remove {platformName}', diff --git a/modules/Admin/Language/it/Podcast.php b/modules/Admin/Language/it/Podcast.php index 426b763b..ff0daebc 100644 --- a/modules/Admin/Language/it/Podcast.php +++ b/modules/Admin/Language/it/Podcast.php @@ -13,6 +13,7 @@ return [ 'no_podcast' => 'No podcast found!', 'create' => 'Create podcast', 'import' => 'Import podcast', + 'all_imports' => 'Podcast imports', 'new_episode' => 'New Episode', 'view' => 'View podcast', 'edit' => 'Edit podcast', @@ -21,6 +22,7 @@ return [ 'delete' => 'Delete podcast', 'see_episodes' => 'See episodes', 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', 'go_to_page' => 'Go to page', 'latest_episodes' => 'Latest episodes', 'see_all_episodes' => 'See all episodes', @@ -48,7 +50,6 @@ return [ other {# episodes were} } added to the podcast!', 'podcastFeedUpToDate' => 'Podcast is already up to date.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', 'publishError' => 'This podcast is either already published or scheduled for publication.', 'publishEditError' => 'This podcast is not scheduled for publication.', 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', @@ -57,6 +58,8 @@ return [ 'form' => [ 'identity_section_title' => 'Podcast identity', 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + 'cover' => 'Podcast cover', 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', 'banner' => 'Podcast banner', @@ -71,7 +74,17 @@ return [ 'episodic' => 'Episodic', 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', 'serial' => 'Serial', - 'serial_hint' => 'If episodes are intended to be consumed in sequential order. The oldest episodes will be presented first.', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', ], 'description' => 'Description', 'classification_section_title' => 'Classification', @@ -96,6 +109,8 @@ return [ 'owner_email' => 'Owner email', 'owner_email_hint' => 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', 'publisher' => 'Publisher', 'publisher_hint' => 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', @@ -110,6 +125,11 @@ return [ 'premium' => 'Premium', 'premium_by_default' => 'Episodes must be set as premium by default', 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', 'payment_pointer' => 'Payment Pointer for Web Monetization', 'payment_pointer_hint' => 'This is your where you will receive money thanks to Web Monetization', @@ -118,11 +138,12 @@ return [ 'If you need RSS tags that Castopod does not handle, set them here.', 'custom_rss' => 'Custom RSS tags for the podcast', 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', 'new_feed_url' => 'New feed URL', 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', 'partnership' => 'Partnership', 'partner_id' => 'ID', 'partner_link_url' => 'Link URL', @@ -130,7 +151,6 @@ return [ 'partner_id_hint' => 'Your own partner ID', 'partner_link_url_hint' => 'The generic partner link address', 'partner_image_url_hint' => 'The generic partner image address', - 'status_section_title' => 'Status', 'block' => 'Podcast should be hidden from public catalogues', 'block_hint' => 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', diff --git a/modules/Admin/Language/it/PodcastImport.php b/modules/Admin/Language/it/PodcastImport.php deleted file mode 100644 index 7c3ef67d..00000000 --- a/modules/Admin/Language/it/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'The podcast to import', - 'old_podcast_section_subtitle' => - 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', - 'imported_feed_url' => 'Feed URL', - 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', - 'new_podcast_section_title' => 'The new podcast', - 'advanced_params_section_title' => 'Advanced parameters', - 'advanced_params_section_subtitle' => - 'Keep the default values if you have no idea of what the fields are for.', - 'slug_field' => 'Field to be used to calculate episode slug', - 'description_field' => - 'Source field used for episode description / show notes', - 'force_renumber' => 'Force episodes renumbering', - 'force_renumber_hint' => - 'Use this if your podcast does not have episode numbers but wish to set them during import.', - 'season_number' => 'Season number', - 'season_number_hint' => - 'Use this if your podcast does not have a season number but wish to set one during import. Leave blank otherwise.', - 'max_episodes' => 'Maximum number of episodes to import', - 'max_episodes_hint' => 'Leave blank to import all episodes', - 'lock_import' => - 'This feed is protected. You cannot import it. If you are the owner, unprotect it on the origin platform.', - 'submit' => 'Import podcast', -]; diff --git a/modules/Admin/Language/it/PodcastNavigation.php b/modules/Admin/Language/it/PodcastNavigation.php index b4d7ddc0..bb777707 100644 --- a/modules/Admin/Language/it/PodcastNavigation.php +++ b/modules/Admin/Language/it/PodcastNavigation.php @@ -10,10 +10,13 @@ declare(strict_types=1); return [ 'go_to_page' => 'Go to podcast page', + 'rss_feed' => 'RSS feed', 'dashboard' => 'Podcast dashboard', 'podcast-view' => 'Home', 'podcast-edit' => 'Edit podcast', 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', 'episodes' => 'Episodes', 'episode-list' => 'All episodes', 'episode-create' => 'New episode', @@ -25,14 +28,15 @@ return [ 'podcast-analytics-players' => 'Players', 'podcast-analytics-listening-time' => 'Listening time', 'podcast-analytics-time-periods' => 'Time periods', - 'premium' => 'Premium', + 'monetization' => 'Monetization', 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', + 'subscription-create' => 'Add subscription', 'contributors' => 'Contributors', 'contributor-list' => 'All contributors', 'contributor-add' => 'Add contributor', - 'platforms' => 'External platforms', - 'platforms-podcasting' => 'Podcasting', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', 'platforms-social' => 'Social networks', - 'platforms-funding' => 'Funding', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', ]; diff --git a/modules/Admin/Language/it/User.php b/modules/Admin/Language/it/User.php deleted file mode 100644 index 585d6799..00000000 --- a/modules/Admin/Language/it/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', - 'ban' => 'Ban', - 'unban' => 'Unban', - 'delete' => 'Delete', - 'create' => 'New user', - 'view' => "{username}'s info", - 'all_users' => 'All users', - 'list' => [ - 'user' => 'User', - 'roles' => 'Roles', - 'banned' => 'Banned?', - ], - 'form' => [ - 'email' => 'Email', - 'username' => 'Username', - 'password' => 'Password', - 'new_password' => 'New Password', - 'roles' => 'Roles', - 'permissions' => 'Permissions', - 'submit_create' => 'Create user', - 'submit_edit' => 'Save', - 'submit_password_change' => 'Change!', - ], - 'roles' => [ - 'superadmin' => 'Super admin', - ], - 'messages' => [ - 'createSuccess' => - 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => - "{username}'s roles have been successfully updated.", - 'forcePassResetSuccess' => - '{username} will be prompted with a password reset upon next visit.', - 'banSuccess' => '{username} has been banned.', - 'unbanSuccess' => '{username} has been unbanned.', - 'editOwnerError' => - '{username} is the instance owner, you cannot edit its roles.', - 'banSuperAdminError' => - '{username} is a superadmin, one does not simply ban a superadmin…', - 'deleteSuperAdminError' => - '{username} is a superadmin, one does not simply delete a superadmin…', - 'deleteSuccess' => '{username} has been deleted.', - ], -]; diff --git a/modules/Admin/Language/it/Validation.php b/modules/Admin/Language/it/Validation.php index 750b1968..f76c3163 100644 --- a/modules/Admin/Language/it/Validation.php +++ b/modules/Admin/Language/it/Validation.php @@ -13,6 +13,5 @@ return [ '{field} is either not an image, or it is not wide or tall enough.', 'is_image_ratio' => '{field} is either not an image or not of the right ratio.', - 'validate_url' => - 'The {field} field must be a valid URL (eg. https://example.com/).', + 'is_json' => '{field} contains invalid JSON.', ]; diff --git a/modules/Admin/Language/ja/AboutCastopod.php b/modules/Admin/Language/ja/AboutCastopod.php new file mode 100644 index 00000000..16c3c2b2 --- /dev/null +++ b/modules/Admin/Language/ja/AboutCastopod.php @@ -0,0 +1,22 @@ + 'Castopodについて', + 'host_name' => 'ホスト名', + 'version' => 'Castopodバージョン', + 'php_version' => 'PHPバージョン', + 'os' => '(OS) オペレーティング システム', + 'languages' => '言語', + 'update_database' => 'データベースを更新', + 'messages' => [ + 'databaseUpdateSuccess' => 'データベースは最新です', + ], +]; diff --git a/modules/Admin/Language/ja/Breadcrumb.php b/modules/Admin/Language/ja/Breadcrumb.php new file mode 100644 index 00000000..408c9f9f --- /dev/null +++ b/modules/Admin/Language/ja/Breadcrumb.php @@ -0,0 +1,57 @@ + 'breadcrumb', + config('Admin') + ->gateway => 'Home', + 'podcasts' => 'podcasts', + 'episodes' => 'episodes', + 'subscriptions' => 'subscriptions', + 'contributors' => 'contributors', + 'pages' => 'pages', + 'settings' => 'settings', + 'theme' => 'theme', + 'about' => 'about', + 'add' => 'add', + 'new' => 'new', + 'edit' => 'edit', + 'persons' => 'persons', + 'publish' => 'publish', + 'publish-edit' => 'edit publication', + 'publish-date-edit' => 'edit publication date', + 'unpublish' => 'unpublish', + 'delete' => 'delete', + 'remove' => 'remove', + 'fediverse' => 'fediverse', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', + 'users' => 'users', + 'my-account' => 'my account', + 'change-password' => 'change password', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', + 'platforms' => 'platforms', + 'social' => 'social networks', + 'funding' => 'funding', + 'monetization-other' => 'other monetization', + 'analytics' => 'analytics', + 'locations' => 'locations', + 'webpages' => 'web pages', + 'unique-listeners' => 'unique listeners', + 'players' => 'players', + 'listening-time' => 'listening time', + 'time-periods' => 'time periods', + 'soundbites' => 'soundbites', + 'video-clips' => 'video clips', + 'embed' => 'embeddable player', + 'notifications' => 'notifications', + 'suspend' => 'suspend', +]; diff --git a/modules/Admin/Language/ja/Charts.php b/modules/Admin/Language/ja/Charts.php new file mode 100644 index 00000000..6ede2510 --- /dev/null +++ b/modules/Admin/Language/ja/Charts.php @@ -0,0 +1,41 @@ + 'Episode downloads by service (for the past week)', + 'by_player_weekly' => 'Episode downloads by player (for the past week)', + 'by_player_yearly' => 'Episode downloads by player (for the past year)', + 'by_device_weekly' => 'Episode downloads by device (for the past week)', + 'by_os_weekly' => 'Episode downloads by O.S. (for the past week)', + 'podcast_by_region' => 'Episode downloads by region (for the past week)', + 'unique_daily_listeners' => 'Daily unique listeners', + 'unique_monthly_listeners' => 'Monthly unique listeners', + 'by_browser' => 'Web pages usage by browser (for the past week)', + 'podcast_by_day' => 'Episode daily downloads', + 'podcast_by_month' => 'Episode monthly downloads', + 'episode_by_day' => 'Episode daily downloads (first 60 days)', + 'episode_by_month' => 'Episode monthly downloads', + 'episodes_by_day' => + '5 latest episodes downloads (during their first 60 days)', + 'by_country_weekly' => 'Episode downloads by country (for the past week)', + 'by_country_yearly' => 'Episode downloads by country (for the past year)', + 'by_domain_weekly' => 'Web pages visits by source (for the past week)', + 'by_domain_yearly' => 'Web pages visits by source (for the past year)', + 'by_entry_page' => 'Web pages visits by landing page (for the past week)', + 'podcast_bots' => 'Bots (crawlers)', + 'daily_listening_time' => 'Daily cumulative listening time', + 'monthly_listening_time' => 'Monthly cumulative listening time', + 'by_weekday' => 'By week day (for the past 60 days)', + 'by_hour' => 'By time of day (for the past 60 days)', + 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', + 'total_storage_by_month' => 'Monthly storage (in MB)', + 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', +]; diff --git a/modules/Admin/Language/ja/Common.php b/modules/Admin/Language/ja/Common.php new file mode 100644 index 00000000..74addcf2 --- /dev/null +++ b/modules/Admin/Language/ja/Common.php @@ -0,0 +1,52 @@ + 'Yes', + 'no' => 'No', + 'cancel' => 'Cancel', + 'optional' => 'Optional', + 'more' => 'More', + 'no_data' => 'No data found!', + 'close' => 'Close', + 'edit' => 'Edit', + 'copy' => 'Copy', + 'copied' => 'Copied!', + 'home' => 'Home', + 'explicit' => 'Explicit', + 'powered_by' => 'Powered by {castopod}', + 'actions' => 'Actions', + 'pageInfo' => 'Page {currentPage} out of {pageCount}', + 'go_back' => 'Go back', + 'forms' => [ + 'editor' => [ + 'write' => 'Write', + 'preview' => 'Preview', + 'help' => 'Powered by markdown', + ], + 'multiSelect' => [ + 'selectText' => 'Press to select', + 'loadingText' => 'Loading…', + 'noResultsText' => 'No results found', + 'noChoicesText' => 'No choices to choose from', + 'maxItemText' => 'Cannot add more items', + ], + 'upload_file' => 'Upload a file', + 'remote_url' => 'Remote URL', + 'save' => 'Save', + ], + 'play_episode_button' => [ + 'play' => 'Play', + 'playing' => 'Playing', + ], + 'size_limit' => 'Size limit: {0}.', + 'choose_interact' => 'Choose how to interact', + 'view' => 'View', +]; diff --git a/modules/Admin/Language/ja/Countries.php b/modules/Admin/Language/ja/Countries.php new file mode 100644 index 00000000..4cd5d9c8 --- /dev/null +++ b/modules/Admin/Language/ja/Countries.php @@ -0,0 +1,264 @@ + 'Andorra', + 'AE' => 'United Arab Emirates', + 'AF' => 'Afghanistan', + 'AG' => 'Antigua and Barbuda', + 'AI' => 'Anguilla', + 'AL' => 'Albania', + 'AM' => 'Armenia', + 'AO' => 'Angola', + 'AQ' => 'Antarctica', + 'AR' => 'Argentina', + 'AS' => 'American Samoa', + 'AT' => 'Austria', + 'AU' => 'Australia', + 'AW' => 'Aruba', + 'AX' => 'Åland Islands', + 'AZ' => 'Azerbaijan', + 'BA' => 'Bosnia and Herzegovina', + 'BB' => 'Barbados', + 'BD' => 'Bangladesh', + 'BE' => 'Belgium', + 'BF' => 'Burkina Faso', + 'BG' => 'Bulgaria', + 'BH' => 'Bahrain', + 'BI' => 'Burundi', + 'BJ' => 'Benin', + 'BL' => 'Saint Barthélemy', + 'BM' => 'Bermuda', + 'BN' => 'Brunei Darussalam', + 'BO' => 'Bolivia, Plurinational State of', + 'BQ' => 'Bonaire, Sint Eustatius and Saba', + 'BR' => 'Brazil', + 'BS' => 'Bahamas', + 'BT' => 'Bhutan', + 'BV' => 'Bouvet Island', + 'BW' => 'Botswana', + 'BY' => 'Belarus', + 'BZ' => 'Belize', + 'CA' => 'Canada', + 'CC' => 'Cocos (Keeling) Islands', + 'CD' => 'Congo, the Democratic Republic of the', + 'CF' => 'Central African Republic', + 'CG' => 'Congo', + 'CH' => 'Switzerland', + 'CI' => "Côte d'Ivoire", + 'CK' => 'Cook Islands', + 'CL' => 'Chile', + 'CM' => 'Cameroon', + 'CN' => 'China', + 'CO' => 'Colombia', + 'CR' => 'Costa Rica', + 'CU' => 'Cuba', + 'CV' => 'Cape Verde', + 'CW' => 'Curaçao', + 'CX' => 'Christmas Island', + 'CY' => 'Cyprus', + 'CZ' => 'Czech Republic', + 'DE' => 'Germany', + 'DJ' => 'Djibouti', + 'DK' => 'Denmark', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'DZ' => 'Algeria', + 'EC' => 'Ecuador', + 'EE' => 'Estonia', + 'EG' => 'Egypt', + 'EH' => 'Western Sahara', + 'ER' => 'Eritrea', + 'ES' => 'Spain', + 'ET' => 'Ethiopia', + 'FI' => 'Finland', + 'FJ' => 'Fiji', + 'FK' => 'Falkland Islands (Malvinas)', + 'FM' => 'Micronesia, Federated States of', + 'FO' => 'Faroe Islands', + 'FR' => 'France', + 'GA' => 'Gabon', + 'GB' => 'United Kingdom', + 'GD' => 'Grenada', + 'GE' => 'Georgia', + 'GF' => 'French Guiana', + 'GG' => 'Guernsey', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GL' => 'Greenland', + 'GM' => 'Gambia', + 'GN' => 'Guinea', + 'GP' => 'Guadeloupe', + 'GQ' => 'Equatorial Guinea', + 'GR' => 'Greece', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'GT' => 'Guatemala', + 'GU' => 'Guam', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HK' => 'Hong Kong', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HR' => 'Croatia', + 'HT' => 'Haiti', + 'HU' => 'Hungary', + 'ID' => 'Indonesia', + 'IE' => 'Ireland', + 'IL' => 'Israel', + 'IM' => 'Isle of Man', + 'IN' => 'India', + 'IO' => 'British Indian Ocean Territory', + 'IQ' => 'Iraq', + 'IR' => 'Iran, Islamic Republic of', + 'IS' => 'Iceland', + 'IT' => 'Italy', + 'JE' => 'Jersey', + 'JM' => 'Jamaica', + 'JO' => 'Jordan', + 'JP' => 'Japan', + 'KE' => 'Kenya', + 'KG' => 'Kyrgyzstan', + 'KH' => 'Cambodia', + 'KI' => 'Kiribati', + 'KM' => 'Comoros', + 'KN' => 'Saint Kitts and Nevis', + 'KP' => "Korea, Democratic People's Republic of", + 'KR' => 'Korea, Republic of', + 'KW' => 'Kuwait', + 'KY' => 'Cayman Islands', + 'KZ' => 'Kazakhstan', + 'LA' => "Lao People's Democratic Republic", + 'LB' => 'Lebanon', + 'LC' => 'Saint Lucia', + 'LI' => 'Liechtenstein', + 'LK' => 'Sri Lanka', + 'LR' => 'Liberia', + 'LS' => 'Lesotho', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'LV' => 'Latvia', + 'LY' => 'Libya', + 'MA' => 'Morocco', + 'MC' => 'Monaco', + 'MD' => 'Moldova, Republic of', + 'ME' => 'Montenegro', + 'MF' => 'Saint Martin (French part)', + 'MG' => 'Madagascar', + 'MH' => 'Marshall Islands', + 'MK' => 'Macedonia, the Former Yugoslav Republic of', + 'ML' => 'Mali', + 'MM' => 'Myanmar', + 'MN' => 'Mongolia', + 'MO' => 'Macao', + 'MP' => 'Northern Mariana Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MS' => 'Montserrat', + 'MT' => 'Malta', + 'MU' => 'Mauritius', + 'MV' => 'Maldives', + 'MW' => 'Malawi', + 'MX' => 'Mexico', + 'MY' => 'Malaysia', + 'MZ' => 'Mozambique', + 'N/A' => 'Not Applicable (local IP…)', + 'NA' => 'Namibia', + 'NC' => 'New Caledonia', + 'NE' => 'Niger', + 'NF' => 'Norfolk Island', + 'NG' => 'Nigeria', + 'NI' => 'Nicaragua', + 'NL' => 'Netherlands', + 'NO' => 'Norway', + 'NP' => 'Nepal', + 'NR' => 'Nauru', + 'NU' => 'Niue', + 'NZ' => 'New Zealand', + 'OM' => 'Oman', + 'PA' => 'Panama', + 'PE' => 'Peru', + 'PF' => 'French Polynesia', + 'PG' => 'Papua New Guinea', + 'PH' => 'Philippines', + 'PK' => 'Pakistan', + 'PL' => 'Poland', + 'PM' => 'Saint Pierre and Miquelon', + 'PN' => 'Pitcairn', + 'PR' => 'Puerto Rico', + 'PS' => 'Palestine, State of', + 'PT' => 'Portugal', + 'PW' => 'Palau', + 'PY' => 'Paraguay', + 'QA' => 'Qatar', + 'RE' => 'Réunion', + 'RO' => 'Romania', + 'RS' => 'Serbia', + 'RU' => 'Russian Federation', + 'RW' => 'Rwanda', + 'SA' => 'Saudi Arabia', + 'SB' => 'Solomon Islands', + 'SC' => 'Seychelles', + 'SD' => 'Sudan', + 'SE' => 'Sweden', + 'SG' => 'Singapore', + 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', + 'SI' => 'Slovenia', + 'SJ' => 'Svalbard and Jan Mayen', + 'SK' => 'Slovakia', + 'SL' => 'Sierra Leone', + 'SM' => 'San Marino', + 'SN' => 'Senegal', + 'SO' => 'Somalia', + 'SR' => 'Suriname', + 'SS' => 'South Sudan', + 'ST' => 'Sao Tome and Principe', + 'SV' => 'El Salvador', + 'SX' => 'Sint Maarten (Dutch part)', + 'SY' => 'Syrian Arab Republic', + 'SZ' => 'Swaziland', + 'TC' => 'Turks and Caicos Islands', + 'TD' => 'Chad', + 'TF' => 'French Southern Territories', + 'TG' => 'Togo', + 'TH' => 'Thailand', + 'TJ' => 'Tajikistan', + 'TK' => 'Tokelau', + 'TL' => 'Timor-Leste', + 'TM' => 'Turkmenistan', + 'TN' => 'Tunisia', + 'TO' => 'Tonga', + 'TR' => 'Turkey', + 'TT' => 'Trinidad and Tobago', + 'TV' => 'Tuvalu', + 'TW' => 'Taiwan, Province of China', + 'TZ' => 'Tanzania, United Republic of', + 'UA' => 'Ukraine', + 'UG' => 'Uganda', + 'UM' => 'United States Minor Outlying Islands', + 'US' => 'United States', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VA' => 'Holy See (Vatican City State)', + 'VC' => 'Saint Vincent and the Grenadines', + 'VE' => 'Venezuela, Bolivarian Republic of', + 'VG' => 'Virgin Islands, British', + 'VI' => 'Virgin Islands, U.S.', + 'VN' => 'Viet Nam', + 'VU' => 'Vanuatu', + 'WF' => 'Wallis and Futuna', + 'WS' => 'Samoa', + 'YE' => 'Yemen', + 'YT' => 'Mayotte', + 'ZA' => 'South Africa', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', +]; diff --git a/modules/Admin/Language/ja/Dashboard.php b/modules/Admin/Language/ja/Dashboard.php new file mode 100644 index 00000000..881073fd --- /dev/null +++ b/modules/Admin/Language/ja/Dashboard.php @@ -0,0 +1,28 @@ + 'Admin dashboard', + 'welcome_message' => 'Welcome to the admin area!', + 'podcasts' => [ + 'title' => 'Podcasts', + 'not_found' => 'No published podcast', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'episodes' => [ + 'title' => 'Episodes', + 'not_found' => 'No published episode', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'storage' => [ + 'title' => 'Storage', + 'subtitle' => '{totalUploaded} out of {totalStorage}', + ], +]; diff --git a/modules/Admin/Language/ja/Episode.php b/modules/Admin/Language/ja/Episode.php new file mode 100644 index 00000000..4fa846e3 --- /dev/null +++ b/modules/Admin/Language/ja/Episode.php @@ -0,0 +1,225 @@ + 'Season {seasonNumber}', + 'season_abbr' => 'S{seasonNumber}', + 'number' => 'Episode {episodeNumber}', + 'number_abbr' => 'Ep. {episodeNumber}', + 'season_episode' => 'Season {seasonNumber} episode {episodeNumber}', + 'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}', + 'number_of_comments' => '{numberOfComments, plural, + one {# comment} + other {# comments} + }', + 'all_podcast_episodes' => 'All podcast episodes', + 'back_to_podcast' => 'Go back to podcast', + 'edit' => 'Edit', + 'preview' => 'Preview', + 'publish' => 'Publish', + 'publish_edit' => 'Edit publication', + 'publish_date_edit' => 'Edit publication date', + 'unpublish' => 'Unpublish', + 'publish_error' => 'Episode is already published.', + 'publish_edit_error' => 'Episode is already published.', + 'publish_cancel_error' => 'Episode is already published.', + 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', + 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', + 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'unpublish_error' => 'Episode is not published.', + 'delete' => 'Delete', + 'go_to_page' => 'Go to page', + 'create' => 'Add an episode', + 'publication_status' => [ + 'published' => 'Published', + 'with_podcast' => 'Published', + 'scheduled' => 'Scheduled', + 'not_published' => 'Not published', + ], + 'with_podcast_hint' => 'To be published at the same time as the podcast', + 'list' => [ + 'search' => [ + 'placeholder' => 'Search for an episode', + 'clear' => 'Clear search', + 'submit' => 'Search', + ], + 'number_of_episodes' => '{numberOfEpisodes, plural, + one {# episode} + other {# episodes} + }', + 'episode' => 'Episode', + 'visibility' => 'Visibility', + 'downloads' => 'Downloads', + 'comments' => 'Comments', + 'actions' => 'Actions', + ], + 'messages' => [ + 'createSuccess' => 'Episode has been successfully created!', + 'editSuccess' => 'Episode has been successfully updated!', + 'publishSuccess' => '{publication_status, select, + published {Episode successfully published!} + scheduled {Episode publication successfully scheduled!} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not published.} + }', + 'publishCancelSuccess' => 'Episode publication successfully cancelled!', + 'unpublishBeforeDeleteTip' => 'You must unpublish the episode before deleting it.', + 'scheduleDateError' => 'Schedule date must be set!', + 'deletePublishedEpisodeError' => 'Please unpublish the episode before deleting it.', + 'deleteSuccess' => 'Episode successfully deleted!', + 'deleteError' => 'Failed to delete episode {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deleteFileError' => 'Failed to delete {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + } file {file_key}. You may manually remove it from your disk.', + 'sameSlugError' => 'An episode with the chosen slug already exists.', + ], + 'form' => [ + 'file_size_error' => + 'Your file size is too big! Max size is {0}. Increase the `memory_limit`, `upload_max_filesize` and `post_max_size` values in your php configuration file then restart your web server to upload your file.', + 'audio_file' => 'Audio file', + 'audio_file_hint' => 'Choose an .mp3 or .m4a audio file.', + 'info_section_title' => 'Episode info', + 'cover' => 'Episode cover', + 'cover_hint' => + 'If you do not set a cover, the podcast cover will be used instead.', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'title' => 'Title', + 'title_hint' => + 'Should contain a clear and concise episode name. Do not specify the episode or season numbers here.', + 'permalink' => 'Permalink', + 'season_number' => 'Season', + 'episode_number' => 'Episode', + 'type' => [ + 'label' => 'Type', + 'full' => 'Full', + 'full_hint' => 'Complete content (the episode)', + 'trailer' => 'Trailer', + 'trailer_hint' => 'Short, promotional piece of content that represents a preview of the current show', + 'bonus' => 'Bonus', + 'bonus_hint' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show', + ], + 'premium_title' => 'Premium', + 'premium' => 'Episode must be accessible to premium subscribers only', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does the episode contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'show_notes_section_title' => 'Show notes', + 'show_notes_section_subtitle' => + 'Up to 4000 characters, be clear and concise. Show notes help potential listeners in finding the episode.', + 'description' => 'Description', + 'description_footer' => 'Description footer', + 'description_footer_hint' => + 'This text is added at the end of each episode description, it is a good place to input your social links for example.', + 'additional_files_section_title' => 'Additional files', + 'additional_files_section_subtitle' => + 'These files may be used by other platforms to provide better experience to your audience. See the {podcastNamespaceLink} for more information.', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this episode about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real or fictional location', + 'transcript' => 'Transcript (subtitles / closed captions)', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', + 'transcript_download' => 'Download transcript', + 'transcript_file' => 'Transcript file (.srt or .vtt)', + 'transcript_remote_url' => 'Remote url for transcript', + 'transcript_file_delete' => 'Delete transcript file', + 'chapters' => 'Chapters', + 'chapters_hint' => 'File must be in JSON Chapters format.', + 'chapters_download' => 'Download chapters', + 'chapters_file' => 'Chapters file', + 'chapters_remote_url' => 'Remote url for chapters file', + 'chapters_file_delete' => 'Delete chapters file', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the episode', + 'custom_rss_hint' => 'This will be injected within the ❬item❭ tag.', + 'block' => 'Episode should be hidden from public catalogues', + 'block_hint' => + 'The episode show or hide status: toggling this on prevents the episode from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'submit_create' => 'Create episode', + 'submit_edit' => 'Save episode', + ], + 'publish_form' => [ + 'back_to_episode_dashboard' => 'Back to episode dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your episode. The message will be broadcasted to all your followers in the fediverse and be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + 'with_podcast' => 'Publish alongside podcast', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_clear' => 'Clear publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the episode release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit' => 'Publish', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your episode.', + 'message_warning_submit' => 'Publish anyways', + ], + 'publish_date_edit_form' => [ + 'new_publication_date' => 'New publication date', + 'new_publication_date_hint' => 'Must be set to a past date.', + 'submit' => 'Edit publication date', + ], + 'unpublish_form' => [ + 'disclaimer' => + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", + 'understand' => 'I understand, I want to unpublish the episode', + 'submit' => 'Unpublish', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.", + 'understand' => 'I understand, I want to delete the episode', + 'submit' => 'Delete', + ], + 'embed' => [ + 'title' => 'Embeddable player', + 'label' => + 'Pick a theme color, copy the embeddable player to clipboard, then paste it on your website.', + 'clipboard_iframe' => 'Copy embeddable player to clipboard', + 'clipboard_url' => 'Copy address to clipboard', + 'dark' => 'Dark', + 'dark-transparent' => 'Dark transparent', + 'light' => 'Light', + 'light-transparent' => 'Light transparent', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], +]; diff --git a/modules/Admin/Language/ja/EpisodeNavigation.php b/modules/Admin/Language/ja/EpisodeNavigation.php new file mode 100644 index 00000000..1406e301 --- /dev/null +++ b/modules/Admin/Language/ja/EpisodeNavigation.php @@ -0,0 +1,23 @@ + 'View episode page', + 'dashboard' => 'Episode dashboard', + 'episode-view' => 'Home', + 'episode-edit' => 'Edit episode', + 'episode-persons-manage' => 'Manage persons', + 'embed-add' => 'Embeddable player', + 'clips' => 'Clips', + 'video-clips-list' => 'Video clips', + 'video-clips-create' => 'New video clip', + 'soundbites-list' => 'Soundbites', + 'soundbites-create' => 'New soundbite', +]; diff --git a/modules/Admin/Language/ja/Fediverse.php b/modules/Admin/Language/ja/Fediverse.php new file mode 100644 index 00000000..0e4ca66d --- /dev/null +++ b/modules/Admin/Language/ja/Fediverse.php @@ -0,0 +1,32 @@ + [ + 'actorNotFound' => 'The account could not be found!', + 'blockActorSuccess' => '{actor} has been blocked!', + 'unblockActorSuccess' => 'Actor has been unblocked!', + 'blockDomainSuccess' => '{domain} has been blocked!', + 'unblockDomainSuccess' => '{domain} has been unblocked!', + ], + 'blocked_actors' => 'Blocked accounts', + 'blocked_domains' => 'Blocked domains', + 'block_lists_form' => [ + 'handle' => 'Account handle', + 'handle_hint' => 'Input @username@domain account.', + 'domain' => 'Domain name', + 'submit' => 'Block!', + ], + 'list' => [ + 'actor' => 'Account', + 'domain' => 'Domain name', + 'unblock' => 'Unblock', + ], +]; diff --git a/modules/Admin/Language/ja/Home.php b/modules/Admin/Language/ja/Home.php new file mode 100644 index 00000000..3ff4c04d --- /dev/null +++ b/modules/Admin/Language/ja/Home.php @@ -0,0 +1,14 @@ + 'All podcasts', + 'no_podcast' => 'No podcast found', +]; diff --git a/modules/Admin/Language/ja/Install.php b/modules/Admin/Language/ja/Install.php new file mode 100644 index 00000000..36e373a2 --- /dev/null +++ b/modules/Admin/Language/ja/Install.php @@ -0,0 +1,61 @@ + 'Manual configuration', + 'manual_config_subtitle' => + 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'form' => [ + 'instance_config' => 'Instance configuration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', + 'db_hostname' => 'Database hostname', + 'db_name' => 'Database name', + 'db_username' => 'Database username', + 'db_password' => 'Database password', + 'db_prefix' => 'Database prefix', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Cache handler', + 'cacheHandlerOptions' => [ + 'file' => 'File', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Next', + 'submit' => 'Finish install', + 'create_superadmin' => 'Create your superadmin account', + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Your superadmin account has been created successfully. Login to start podcasting!', + 'databaseConnectError' => + 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'writeError' => + "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + ], +]; diff --git a/modules/Admin/Language/ja/Navigation.php b/modules/Admin/Language/ja/Navigation.php new file mode 100644 index 00000000..f3ffb129 --- /dev/null +++ b/modules/Admin/Language/ja/Navigation.php @@ -0,0 +1,44 @@ + 'Toggle sidebar', + 'go_to_website' => 'Go to website', + 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', + 'dashboard' => 'Dashboard', + 'admin' => 'Home', + 'podcasts' => 'Podcasts', + 'podcast-list' => 'All podcasts', + 'podcast-create' => 'New podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', + 'persons' => 'Persons', + 'person-list' => 'All persons', + 'person-create' => 'New person', + 'fediverse' => 'Fediverse', + 'fediverse-blocked-actors' => 'Blocked accounts', + 'fediverse-blocked-domains' => 'Blocked domains', + 'users' => 'Users', + 'user-list' => 'All users', + 'user-create' => 'New user', + 'pages' => 'Pages', + 'page-list' => 'All pages', + 'page-create' => 'New Page', + 'settings' => 'Settings', + 'settings-general' => 'General', + 'settings-theme' => 'Theme', + 'admin-about' => 'About', + 'account' => [ + 'my-account' => 'My account', + 'change-password' => 'Change password', + 'logout' => 'Logout', + ], +]; diff --git a/modules/Admin/Language/ja/Notifications.php b/modules/Admin/Language/ja/Notifications.php new file mode 100644 index 00000000..2b139d51 --- /dev/null +++ b/modules/Admin/Language/ja/Notifications.php @@ -0,0 +1,19 @@ + 'Notifications', + 'reply' => '{actor_username} replied to your post', + 'favourite' => '{actor_username} favourited your post', + 'reblog' => '{actor_username} shared your post', + 'follow' => '{actor_username} started following you', + 'no_notifications' => 'No notifications', + 'mark_all_as_read' => 'Mark all as read', +]; diff --git a/modules/Admin/Language/ja/Page.php b/modules/Admin/Language/ja/Page.php new file mode 100644 index 00000000..b6f49de5 --- /dev/null +++ b/modules/Admin/Language/ja/Page.php @@ -0,0 +1,30 @@ + 'Back to home', + 'page' => 'Page', + 'all_pages' => 'All pages', + 'create' => 'New page', + 'go_to_page' => 'Go to page', + 'edit' => 'Edit page', + 'delete' => 'Delete page', + 'form' => [ + 'title' => 'Title', + 'permalink' => 'Permalink', + 'content' => 'Content', + 'submit_create' => 'Create page', + 'submit_edit' => 'Save', + ], + 'messages' => [ + 'createSuccess' => 'The page “{pageTitle}” was created successfully!', + 'editSuccess' => 'The page was successfully updated!', + ], +]; diff --git a/modules/Admin/Language/ja/Pager.php b/modules/Admin/Language/ja/Pager.php new file mode 100644 index 00000000..e25ee638 --- /dev/null +++ b/modules/Admin/Language/ja/Pager.php @@ -0,0 +1,21 @@ + 'Page navigation', + 'first' => 'First', + 'previous' => 'Previous', + 'next' => 'Next', + 'last' => 'Last', + 'older' => 'Older', + 'newer' => 'Newer', + 'invalidTemplate' => '{0} is not a valid Pager template.', + 'invalidPaginationGroup' => '{0} is not a valid Pagination group.', +]; diff --git a/modules/Admin/Language/ja/Person.php b/modules/Admin/Language/ja/Person.php new file mode 100644 index 00000000..a652be9f --- /dev/null +++ b/modules/Admin/Language/ja/Person.php @@ -0,0 +1,65 @@ + 'Persons', + 'all_persons' => 'All persons', + 'no_person' => 'Nobody found!', + 'create' => 'Create a person', + 'view' => 'View person', + 'edit' => 'Edit person', + 'delete' => 'Delete person', + 'messages' => [ + 'createSuccess' => 'Person has been successfully created!', + 'editSuccess' => 'Person has been successfully updated!', + 'deleteSuccess' => 'Person has been removed!', + ], + 'form' => [ + 'avatar' => 'Avatar', + 'avatar_size_hint' => + 'Avatar must be squared and at least 400px wide and tall.', + 'full_name' => 'Full name', + 'full_name_hint' => 'This is the full name or alias of the person.', + 'unique_name' => 'Unique name', + 'unique_name_hint' => 'Used for URLs', + 'information_url' => 'Information URL', + 'information_url_hint' => + 'Url to a relevant resource of information about the person, such as a homepage or third-party profile platform.', + 'submit_create' => 'Create person', + 'submit_edit' => 'Save person', + ], + 'podcast_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this podcast', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'episode_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this episode', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'credits' => 'Credits', +]; diff --git a/modules/Admin/Language/ja/Platforms.php b/modules/Admin/Language/ja/Platforms.php new file mode 100644 index 00000000..e161181c --- /dev/null +++ b/modules/Admin/Language/ja/Platforms.php @@ -0,0 +1,43 @@ + [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', + 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', + 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', + 'visible' => 'Display in podcast homepage?', + 'on_embed' => 'Display on embeddable player?', + 'remove' => 'Remove {platformName}', + 'submit' => 'Save', + 'messages' => [ + 'updateSuccess' => 'Platform links have been successfully updated!', + 'removeLinkSuccess' => 'The platform link has been removed.', + 'removeLinkError' => + 'The platform link could not be removed. Try again.', + ], + 'description' => [ + 'podcasting' => 'The podcast ID on this platform', + 'social' => 'The podcast account ID on this platform', + 'funding' => 'Call to action message', + ], +]; diff --git a/modules/Admin/Language/ja/Podcast.php b/modules/Admin/Language/ja/Podcast.php new file mode 100644 index 00000000..ff0daebc --- /dev/null +++ b/modules/Admin/Language/ja/Podcast.php @@ -0,0 +1,330 @@ + 'All podcasts', + 'no_podcast' => 'No podcast found!', + 'create' => 'Create podcast', + 'import' => 'Import podcast', + 'all_imports' => 'Podcast imports', + 'new_episode' => 'New Episode', + 'view' => 'View podcast', + 'edit' => 'Edit podcast', + 'publish' => 'Publish podcast', + 'publish_edit' => 'Edit publication', + 'delete' => 'Delete podcast', + 'see_episodes' => 'See episodes', + 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', + 'go_to_page' => 'Go to page', + 'latest_episodes' => 'Latest episodes', + 'see_all_episodes' => 'See all episodes', + 'draft' => 'Draft', + 'messages' => [ + 'createSuccess' => 'Podcast successfully created!', + 'editSuccess' => 'Podcast has been successfully updated!', + 'importSuccess' => 'Podcast has been successfully imported!', + 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', + 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, + cover {cover} + banner {banner} + other {media} + }.', + 'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.', + 'podcastFeedUpdateSuccess' => 'Successful update: {number_of_new_episodes, plural, + one {# episode was} + other {# episodes were} + } added to the podcast!', + 'podcastFeedUpToDate' => 'Podcast is already up to date.', + 'publishError' => 'This podcast is either already published or scheduled for publication.', + 'publishEditError' => 'This podcast is not scheduled for publication.', + 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', + 'scheduleDateError' => 'Schedule date must be set!', + ], + 'form' => [ + 'identity_section_title' => 'Podcast identity', + 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + + 'cover' => 'Podcast cover', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'banner' => 'Podcast banner', + 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', + 'banner_delete' => 'Delete podcast banner', + 'title' => 'Title', + 'handle' => 'Handle', + 'handle_hint' => + 'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.', + 'type' => [ + 'label' => 'Type', + 'episodic' => 'Episodic', + 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', + 'serial' => 'Serial', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + ], + 'description' => 'Description', + 'classification_section_title' => 'Classification', + 'classification_section_subtitle' => + 'These fields will impact your audience and competition.', + 'language' => 'Language', + 'category' => 'Category', + 'category_placeholder' => 'Select a category…', + 'other_categories' => 'Other categories', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does it contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'author_section_title' => 'Author', + 'author_section_subtitle' => 'Who is managing the podcast?', + 'owner_name' => 'Owner name', + 'owner_name_hint' => + 'For administrative use only. Visible in the public RSS feed.', + 'owner_email' => 'Owner email', + 'owner_email_hint' => + 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', + 'publisher' => 'Publisher', + 'publisher_hint' => + 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', + 'copyright' => 'Copyright', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this podcast about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real place or fictional', + 'monetization_section_title' => 'Monetization', + 'monetization_section_subtitle' => + 'Earn money thanks to your audience.', + 'premium' => 'Premium', + 'premium_by_default' => 'Episodes must be set as premium by default', + 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', + 'payment_pointer' => 'Payment Pointer for Web Monetization', + 'payment_pointer_hint' => + 'This is your where you will receive money thanks to Web Monetization', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the podcast', + 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', + 'new_feed_url' => 'New feed URL', + 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', + 'old_feed_url' => 'Old feed URL', + 'partnership' => 'Partnership', + 'partner_id' => 'ID', + 'partner_link_url' => 'Link URL', + 'partner_image_url' => 'Image URL', + 'partner_id_hint' => 'Your own partner ID', + 'partner_link_url_hint' => 'The generic partner link address', + 'partner_image_url_hint' => 'The generic partner image address', + 'block' => 'Podcast should be hidden from public catalogues', + 'block_hint' => + 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'complete' => 'Podcast will not be having new episodes', + 'lock' => 'Prevent podcast from being copied', + 'lock_hint' => + 'The purpose is to tell other podcast platforms whether they are allowed to import this feed. A value of yes means that any attempt to import this feed into a new platform should be rejected.', + 'submit_create' => 'Create podcast', + 'submit_edit' => 'Save podcast', + ], + 'category_options' => [ + 'uncategorized' => 'uncategorized', + 'arts' => 'Arts', + 'business' => 'Business', + 'comedy' => 'Comedy', + 'education' => 'Education', + 'fiction' => 'Fiction', + 'government' => 'Government', + 'health_and_fitness' => 'Health & Fitness', + 'history' => 'History', + 'kids_and_family' => 'Kids & Family', + 'leisure' => 'Leisure', + 'music' => 'Music', + 'news' => 'News', + 'religion_and_spirituality' => 'Religion & Spirituality', + 'science' => 'Science', + 'society_and_culture' => 'Society & Culture', + 'sports' => 'Sports', + 'technology' => 'Technology', + 'true_crime' => 'True Crime', + 'tv_and_film' => 'TV & Film', + 'books' => 'Books', + 'design' => 'Design', + 'fashion_and_beauty' => 'Fashion & Beauty', + 'food' => 'Food', + 'performing_arts' => 'Performing Arts', + 'visual_arts' => 'Visual Arts', + 'careers' => 'Careers', + 'entrepreneurship' => 'Entrepreneurship', + 'investing' => 'Investing', + 'management' => 'Management', + 'marketing' => 'Marketing', + 'non_profit' => 'Non-Profit', + 'comedy_interviews' => 'Comedy Interviews', + 'improv' => 'Improv', + 'stand_up' => 'Stand-Up', + 'courses' => 'Courses', + 'how_to' => 'How To', + 'language_learning' => 'Language Learning', + 'self_improvement' => 'Self-Improvement', + 'comedy_fiction' => 'Comedy Fiction', + 'drama' => 'Drama', + 'science_fiction' => 'Science Fiction', + 'alternative_health' => 'Alternative Health', + 'fitness' => 'Fitness', + 'medicine' => 'Medicine', + 'mental_health' => 'Mental Health', + 'nutrition' => 'Nutrition', + 'sexuality' => 'Sexuality', + 'education_for_kids' => 'Education for Kids', + 'parenting' => 'Parenting', + 'pets_and_animals' => 'Pets & Animals', + 'stories_for_kids' => 'Stories for Kids', + 'animation_and_manga' => 'Animation & Manga', + 'automotive' => 'Automotive', + 'aviation' => 'Aviation', + 'crafts' => 'Crafts', + 'games' => 'Games', + 'hobbies' => 'Hobbies', + 'home_and_garden' => 'Home & Garden', + 'video_games' => 'Video Games', + 'music_commentary' => 'Music Commentary', + 'music_history' => 'Music History', + 'music_interviews' => 'Music Interviews', + 'business_news' => 'Business News', + 'daily_news' => 'Daily News', + 'entertainment_news' => 'Entertainment News', + 'news_commentary' => 'News Commentary', + 'politics' => 'Politics', + 'sports_news' => 'Sports News', + 'tech_news' => 'Tech News', + 'buddhism' => 'Buddhism', + 'christianity' => 'Christianity', + 'hinduism' => 'Hinduism', + 'islam' => 'Islam', + 'judaism' => 'Judaism', + 'religion' => 'Religion', + 'spirituality' => 'Spirituality', + 'astronomy' => 'Astronomy', + 'chemistry' => 'Chemistry', + 'earth_sciences' => 'Earth Sciences', + 'life_sciences' => 'Life Sciences', + 'mathematics' => 'Mathematics', + 'natural_sciences' => 'Natural Sciences', + 'nature' => 'Nature', + 'physics' => 'Physics', + 'social_sciences' => 'Social Sciences', + 'documentary' => 'Documentary', + 'personal_journals' => 'Personal Journals', + 'philosophy' => 'Philosophy', + 'places_and_travel' => 'Places & Travel', + 'relationships' => 'Relationships', + 'baseball' => 'Baseball', + 'basketball' => 'Basketball', + 'cricket' => 'Cricket', + 'fantasy_sports' => 'Fantasy Sports', + 'football' => 'Football', + 'golf' => 'Golf', + 'hockey' => 'Hockey', + 'rugby' => 'Rugby', + 'running' => 'Running', + 'soccer' => 'Soccer', + 'swimming' => 'Swimming', + 'tennis' => 'Tennis', + 'volleyball' => 'Volleyball', + 'wilderness' => 'Wilderness', + 'wrestling' => 'Wrestling', + 'after_shows' => 'After Shows', + 'film_history' => 'Film History', + 'film_interviews' => 'Film Interviews', + 'film_reviews' => 'Film Reviews', + 'tv_reviews' => 'TV Reviews', + ], + 'publish_form' => [ + 'back_to_podcast_dashboard' => 'Back to podcast dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'submit' => 'Publish', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', + 'message_warning_submit' => 'Publish anyway', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'not_published' => 'This podcast is not yet published.', + 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", + 'understand' => 'I understand, I want the podcast to be permanently deleted', + 'submit' => 'Delete', + ], + 'by' => 'By {publisher}', + 'season' => 'Season {seasonNumber}', + 'list_of_episodes_year' => '{year} episodes ({episodeCount})', + 'list_of_episodes_season' => + 'Season {seasonNumber} episodes ({episodeCount})', + 'no_episode' => 'No episode found!', + 'follow' => 'Follow', + 'followers' => '{numberOfFollowers, plural, + one {# follower} + other {# followers} + }', + 'posts' => '{numberOfPosts, plural, + one {# post} + other {# posts} + }', + 'activity' => 'Activity', + 'episodes' => 'Episodes', + 'sponsor' => 'Sponsor', + 'funding_links' => 'Funding links for {podcastTitle}', + 'find_on' => 'Find {podcastTitle} on', + 'listen_on' => 'Listen on', +]; diff --git a/modules/Admin/Language/ja/PodcastNavigation.php b/modules/Admin/Language/ja/PodcastNavigation.php new file mode 100644 index 00000000..bb777707 --- /dev/null +++ b/modules/Admin/Language/ja/PodcastNavigation.php @@ -0,0 +1,42 @@ + 'Go to podcast page', + 'rss_feed' => 'RSS feed', + 'dashboard' => 'Podcast dashboard', + 'podcast-view' => 'Home', + 'podcast-edit' => 'Edit podcast', + 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', + 'episodes' => 'Episodes', + 'episode-list' => 'All episodes', + 'episode-create' => 'New episode', + 'analytics' => 'Analytics', + 'podcast-analytics' => 'Audience overview', + 'podcast-analytics-webpages' => 'Web pages visits', + 'podcast-analytics-locations' => 'Locations', + 'podcast-analytics-unique-listeners' => 'Unique listeners', + 'podcast-analytics-players' => 'Players', + 'podcast-analytics-listening-time' => 'Listening time', + 'podcast-analytics-time-periods' => 'Time periods', + 'monetization' => 'Monetization', + 'subscription-list' => 'All subscriptions', + 'subscription-create' => 'Add subscription', + 'contributors' => 'Contributors', + 'contributor-list' => 'All contributors', + 'contributor-add' => 'Add contributor', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', + 'platforms-social' => 'Social networks', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', +]; diff --git a/modules/Admin/Language/ja/Settings.php b/modules/Admin/Language/ja/Settings.php new file mode 100644 index 00000000..4a70dcba --- /dev/null +++ b/modules/Admin/Language/ja/Settings.php @@ -0,0 +1,58 @@ + 'General settings', + 'instance' => [ + 'title' => 'Instance', + 'site_icon' => 'Site icon', + 'site_icon_delete' => 'Delete site icon', + 'site_icon_hint' => 'Site icons are what you see on your browser tabs, bookmarks bar, and when you add a website as a shortcut on mobile devices.', + 'site_icon_helper' => 'Icon must be squared and at least 512px wide and tall.', + 'site_name' => 'Site name', + 'site_description' => 'Site description', + 'submit' => 'Save', + 'editSuccess' => 'Instance has been updated successfully!', + 'deleteIconSuccess' => 'Site icon has been remove successfully!', + ], + 'images' => [ + 'title' => 'Images', + 'subtitle' => 'Here you can regenerate all images based on the originals that were uploaded. To be used if you find that some images are missing. This task may take a while.', + 'regenerate' => 'Regenerate images', + 'regenerationSuccess' => 'All images have been regenerated successfully!', + ], + 'housekeeping' => [ + 'title' => 'Housekeeping', + 'subtitle' => 'Runs various housekeeping tasks. Use this feature if you ever encounter issues with media files or data integrity. These tasks may take a while.', + 'reset_counts' => 'Reset counts', + 'reset_counts_helper' => 'This option will recalculate and reset all data counts (number of followers, posts, comments, …).', + 'rewrite_media' => 'Rewrite media metadata', + 'rewrite_media_helper' => 'This option will delete all superfluous media files and recreate them (images, audio files, transcripts, chapters, …)', + 'rename_episodes_files' => 'Rename episode audio files', + 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', + 'clear_cache' => 'Clear all cache', + 'clear_cache_helper' => 'This option will flush redis cache or writable/cache files.', + 'run' => 'Run housekeeping', + 'runSuccess' => 'Housekeeping has been run successfully!', + ], + 'theme' => [ + 'title' => 'Theme', + 'accent_section_title' => 'Accent color', + 'accent_section_subtitle' => 'Choose the color to determine the look and feel of all public pages.', + 'pine' => 'Pine', + 'crimson' => 'Crimson', + 'amber' => 'Amber', + 'lake' => 'Lake', + 'jacaranda' => 'Jacaranda', + 'onyx' => 'Onyx', + 'submit' => 'Save', + 'setInstanceThemeSuccess' => 'Theme has been updated successfully!', + ], +]; diff --git a/modules/Admin/Language/ja/Soundbite.php b/modules/Admin/Language/ja/Soundbite.php new file mode 100644 index 00000000..a3f828fe --- /dev/null +++ b/modules/Admin/Language/ja/Soundbite.php @@ -0,0 +1,31 @@ + [ + 'title' => 'Soundbites', + 'soundbite' => 'Soundbite', + ], + 'messages' => [ + 'createSuccess' => 'Soundbite has been successfully created!', + 'deleteSuccess' => 'Soundbite has been successfully removed!', + ], + 'form' => [ + 'title' => 'New soundbite', + 'soundbite_title' => 'Soundbite title', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'submit' => 'Create soundbite', + ], + 'play' => 'Play soundbite', + 'stop' => 'Stop soundbite', + 'create' => 'New soundbite', + 'delete' => 'Delete soundbite', +]; diff --git a/modules/Admin/Language/ja/Validation.php b/modules/Admin/Language/ja/Validation.php new file mode 100644 index 00000000..f76c3163 --- /dev/null +++ b/modules/Admin/Language/ja/Validation.php @@ -0,0 +1,17 @@ + + '{field} is either not an image, or it is not wide or tall enough.', + 'is_image_ratio' => + '{field} is either not an image or not of the right ratio.', + 'is_json' => '{field} contains invalid JSON.', +]; diff --git a/modules/Admin/Language/ja/VideoClip.php b/modules/Admin/Language/ja/VideoClip.php new file mode 100644 index 00000000..638de697 --- /dev/null +++ b/modules/Admin/Language/ja/VideoClip.php @@ -0,0 +1,72 @@ + [ + 'title' => 'Video clips', + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Clip is waiting to be processed.', + 'pending' => 'pending', + 'pending_hint' => 'Clip will be generated shortly.', + 'running' => 'running', + 'running_hint' => 'Clip is being generated.', + 'failed' => 'failed', + 'failed_hint' => 'Clip could not be generated: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Clip was generated successfully!', + ], + 'clip' => 'Clip', + 'duration' => 'Job duration', + ], + 'title' => 'Video clip: {videoClipLabel}', + 'download_clip' => 'Download clip', + 'create' => 'New video clip', + 'go_to_page' => 'Go to clip page', + 'retry' => 'Retry clip generation', + 'delete' => 'Delete clip', + 'logs' => 'Job logs', + 'messages' => [ + 'alreadyExistingError' => 'The video clip you are trying to create already exists!', + 'addToQueueSuccess' => 'Video clip has been added to queue, awaiting to be created!', + 'deleteSuccess' => 'Video clip has been successfully removed!', + ], + 'format' => [ + 'landscape' => 'Landscape', + 'portrait' => 'Portrait', + 'squared' => 'Squared', + ], + 'form' => [ + 'title' => 'New video clip', + 'params_section_title' => 'Video clip parameters', + 'clip_title' => 'Clip title', + 'format' => [ + 'label' => 'Choose a format', + 'landscape_hint' => 'With a 16:9 ratio, landscape videos are great for PeerTube, Youtube and Vimeo.', + 'portrait_hint' => 'With a 9:16 ratio, portrait videos are great for TikTok, Youtube shorts and Instagram stories.', + 'squared_hint' => 'With a 1:1 ratio, squared videos are great for Mastodon, Facebook, Twitter and LinkedIn.', + ], + 'theme' => 'Select a theme', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'trim_start' => 'Trim start', + 'trim_end' => 'Trim end', + 'submit' => 'Create video clip', + ], + 'requirements' => [ + 'title' => 'Missing requirements', + 'missing' => 'You have missing requirements. Make sure to add all the required items to be allowed creating a video for this episode!', + 'ffmpeg' => 'FFmpeg', + 'gd' => 'Graphics Draw (GD)', + 'freetype' => 'Freetype library for GD', + 'transcript' => 'Transcript file (.srt)', + ], +]; diff --git a/modules/Admin/Language/kk/AboutCastopod.php b/modules/Admin/Language/kk/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/kk/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/kk/Breadcrumb.php b/modules/Admin/Language/kk/Breadcrumb.php new file mode 100644 index 00000000..408c9f9f --- /dev/null +++ b/modules/Admin/Language/kk/Breadcrumb.php @@ -0,0 +1,57 @@ + 'breadcrumb', + config('Admin') + ->gateway => 'Home', + 'podcasts' => 'podcasts', + 'episodes' => 'episodes', + 'subscriptions' => 'subscriptions', + 'contributors' => 'contributors', + 'pages' => 'pages', + 'settings' => 'settings', + 'theme' => 'theme', + 'about' => 'about', + 'add' => 'add', + 'new' => 'new', + 'edit' => 'edit', + 'persons' => 'persons', + 'publish' => 'publish', + 'publish-edit' => 'edit publication', + 'publish-date-edit' => 'edit publication date', + 'unpublish' => 'unpublish', + 'delete' => 'delete', + 'remove' => 'remove', + 'fediverse' => 'fediverse', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', + 'users' => 'users', + 'my-account' => 'my account', + 'change-password' => 'change password', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', + 'platforms' => 'platforms', + 'social' => 'social networks', + 'funding' => 'funding', + 'monetization-other' => 'other monetization', + 'analytics' => 'analytics', + 'locations' => 'locations', + 'webpages' => 'web pages', + 'unique-listeners' => 'unique listeners', + 'players' => 'players', + 'listening-time' => 'listening time', + 'time-periods' => 'time periods', + 'soundbites' => 'soundbites', + 'video-clips' => 'video clips', + 'embed' => 'embeddable player', + 'notifications' => 'notifications', + 'suspend' => 'suspend', +]; diff --git a/modules/Admin/Language/kk/Charts.php b/modules/Admin/Language/kk/Charts.php new file mode 100644 index 00000000..6ede2510 --- /dev/null +++ b/modules/Admin/Language/kk/Charts.php @@ -0,0 +1,41 @@ + 'Episode downloads by service (for the past week)', + 'by_player_weekly' => 'Episode downloads by player (for the past week)', + 'by_player_yearly' => 'Episode downloads by player (for the past year)', + 'by_device_weekly' => 'Episode downloads by device (for the past week)', + 'by_os_weekly' => 'Episode downloads by O.S. (for the past week)', + 'podcast_by_region' => 'Episode downloads by region (for the past week)', + 'unique_daily_listeners' => 'Daily unique listeners', + 'unique_monthly_listeners' => 'Monthly unique listeners', + 'by_browser' => 'Web pages usage by browser (for the past week)', + 'podcast_by_day' => 'Episode daily downloads', + 'podcast_by_month' => 'Episode monthly downloads', + 'episode_by_day' => 'Episode daily downloads (first 60 days)', + 'episode_by_month' => 'Episode monthly downloads', + 'episodes_by_day' => + '5 latest episodes downloads (during their first 60 days)', + 'by_country_weekly' => 'Episode downloads by country (for the past week)', + 'by_country_yearly' => 'Episode downloads by country (for the past year)', + 'by_domain_weekly' => 'Web pages visits by source (for the past week)', + 'by_domain_yearly' => 'Web pages visits by source (for the past year)', + 'by_entry_page' => 'Web pages visits by landing page (for the past week)', + 'podcast_bots' => 'Bots (crawlers)', + 'daily_listening_time' => 'Daily cumulative listening time', + 'monthly_listening_time' => 'Monthly cumulative listening time', + 'by_weekday' => 'By week day (for the past 60 days)', + 'by_hour' => 'By time of day (for the past 60 days)', + 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', + 'total_storage_by_month' => 'Monthly storage (in MB)', + 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', +]; diff --git a/modules/Admin/Language/kk/Common.php b/modules/Admin/Language/kk/Common.php new file mode 100644 index 00000000..74addcf2 --- /dev/null +++ b/modules/Admin/Language/kk/Common.php @@ -0,0 +1,52 @@ + 'Yes', + 'no' => 'No', + 'cancel' => 'Cancel', + 'optional' => 'Optional', + 'more' => 'More', + 'no_data' => 'No data found!', + 'close' => 'Close', + 'edit' => 'Edit', + 'copy' => 'Copy', + 'copied' => 'Copied!', + 'home' => 'Home', + 'explicit' => 'Explicit', + 'powered_by' => 'Powered by {castopod}', + 'actions' => 'Actions', + 'pageInfo' => 'Page {currentPage} out of {pageCount}', + 'go_back' => 'Go back', + 'forms' => [ + 'editor' => [ + 'write' => 'Write', + 'preview' => 'Preview', + 'help' => 'Powered by markdown', + ], + 'multiSelect' => [ + 'selectText' => 'Press to select', + 'loadingText' => 'Loading…', + 'noResultsText' => 'No results found', + 'noChoicesText' => 'No choices to choose from', + 'maxItemText' => 'Cannot add more items', + ], + 'upload_file' => 'Upload a file', + 'remote_url' => 'Remote URL', + 'save' => 'Save', + ], + 'play_episode_button' => [ + 'play' => 'Play', + 'playing' => 'Playing', + ], + 'size_limit' => 'Size limit: {0}.', + 'choose_interact' => 'Choose how to interact', + 'view' => 'View', +]; diff --git a/modules/Admin/Language/kk/Countries.php b/modules/Admin/Language/kk/Countries.php new file mode 100644 index 00000000..4cd5d9c8 --- /dev/null +++ b/modules/Admin/Language/kk/Countries.php @@ -0,0 +1,264 @@ + 'Andorra', + 'AE' => 'United Arab Emirates', + 'AF' => 'Afghanistan', + 'AG' => 'Antigua and Barbuda', + 'AI' => 'Anguilla', + 'AL' => 'Albania', + 'AM' => 'Armenia', + 'AO' => 'Angola', + 'AQ' => 'Antarctica', + 'AR' => 'Argentina', + 'AS' => 'American Samoa', + 'AT' => 'Austria', + 'AU' => 'Australia', + 'AW' => 'Aruba', + 'AX' => 'Åland Islands', + 'AZ' => 'Azerbaijan', + 'BA' => 'Bosnia and Herzegovina', + 'BB' => 'Barbados', + 'BD' => 'Bangladesh', + 'BE' => 'Belgium', + 'BF' => 'Burkina Faso', + 'BG' => 'Bulgaria', + 'BH' => 'Bahrain', + 'BI' => 'Burundi', + 'BJ' => 'Benin', + 'BL' => 'Saint Barthélemy', + 'BM' => 'Bermuda', + 'BN' => 'Brunei Darussalam', + 'BO' => 'Bolivia, Plurinational State of', + 'BQ' => 'Bonaire, Sint Eustatius and Saba', + 'BR' => 'Brazil', + 'BS' => 'Bahamas', + 'BT' => 'Bhutan', + 'BV' => 'Bouvet Island', + 'BW' => 'Botswana', + 'BY' => 'Belarus', + 'BZ' => 'Belize', + 'CA' => 'Canada', + 'CC' => 'Cocos (Keeling) Islands', + 'CD' => 'Congo, the Democratic Republic of the', + 'CF' => 'Central African Republic', + 'CG' => 'Congo', + 'CH' => 'Switzerland', + 'CI' => "Côte d'Ivoire", + 'CK' => 'Cook Islands', + 'CL' => 'Chile', + 'CM' => 'Cameroon', + 'CN' => 'China', + 'CO' => 'Colombia', + 'CR' => 'Costa Rica', + 'CU' => 'Cuba', + 'CV' => 'Cape Verde', + 'CW' => 'Curaçao', + 'CX' => 'Christmas Island', + 'CY' => 'Cyprus', + 'CZ' => 'Czech Republic', + 'DE' => 'Germany', + 'DJ' => 'Djibouti', + 'DK' => 'Denmark', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'DZ' => 'Algeria', + 'EC' => 'Ecuador', + 'EE' => 'Estonia', + 'EG' => 'Egypt', + 'EH' => 'Western Sahara', + 'ER' => 'Eritrea', + 'ES' => 'Spain', + 'ET' => 'Ethiopia', + 'FI' => 'Finland', + 'FJ' => 'Fiji', + 'FK' => 'Falkland Islands (Malvinas)', + 'FM' => 'Micronesia, Federated States of', + 'FO' => 'Faroe Islands', + 'FR' => 'France', + 'GA' => 'Gabon', + 'GB' => 'United Kingdom', + 'GD' => 'Grenada', + 'GE' => 'Georgia', + 'GF' => 'French Guiana', + 'GG' => 'Guernsey', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GL' => 'Greenland', + 'GM' => 'Gambia', + 'GN' => 'Guinea', + 'GP' => 'Guadeloupe', + 'GQ' => 'Equatorial Guinea', + 'GR' => 'Greece', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'GT' => 'Guatemala', + 'GU' => 'Guam', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HK' => 'Hong Kong', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HR' => 'Croatia', + 'HT' => 'Haiti', + 'HU' => 'Hungary', + 'ID' => 'Indonesia', + 'IE' => 'Ireland', + 'IL' => 'Israel', + 'IM' => 'Isle of Man', + 'IN' => 'India', + 'IO' => 'British Indian Ocean Territory', + 'IQ' => 'Iraq', + 'IR' => 'Iran, Islamic Republic of', + 'IS' => 'Iceland', + 'IT' => 'Italy', + 'JE' => 'Jersey', + 'JM' => 'Jamaica', + 'JO' => 'Jordan', + 'JP' => 'Japan', + 'KE' => 'Kenya', + 'KG' => 'Kyrgyzstan', + 'KH' => 'Cambodia', + 'KI' => 'Kiribati', + 'KM' => 'Comoros', + 'KN' => 'Saint Kitts and Nevis', + 'KP' => "Korea, Democratic People's Republic of", + 'KR' => 'Korea, Republic of', + 'KW' => 'Kuwait', + 'KY' => 'Cayman Islands', + 'KZ' => 'Kazakhstan', + 'LA' => "Lao People's Democratic Republic", + 'LB' => 'Lebanon', + 'LC' => 'Saint Lucia', + 'LI' => 'Liechtenstein', + 'LK' => 'Sri Lanka', + 'LR' => 'Liberia', + 'LS' => 'Lesotho', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'LV' => 'Latvia', + 'LY' => 'Libya', + 'MA' => 'Morocco', + 'MC' => 'Monaco', + 'MD' => 'Moldova, Republic of', + 'ME' => 'Montenegro', + 'MF' => 'Saint Martin (French part)', + 'MG' => 'Madagascar', + 'MH' => 'Marshall Islands', + 'MK' => 'Macedonia, the Former Yugoslav Republic of', + 'ML' => 'Mali', + 'MM' => 'Myanmar', + 'MN' => 'Mongolia', + 'MO' => 'Macao', + 'MP' => 'Northern Mariana Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MS' => 'Montserrat', + 'MT' => 'Malta', + 'MU' => 'Mauritius', + 'MV' => 'Maldives', + 'MW' => 'Malawi', + 'MX' => 'Mexico', + 'MY' => 'Malaysia', + 'MZ' => 'Mozambique', + 'N/A' => 'Not Applicable (local IP…)', + 'NA' => 'Namibia', + 'NC' => 'New Caledonia', + 'NE' => 'Niger', + 'NF' => 'Norfolk Island', + 'NG' => 'Nigeria', + 'NI' => 'Nicaragua', + 'NL' => 'Netherlands', + 'NO' => 'Norway', + 'NP' => 'Nepal', + 'NR' => 'Nauru', + 'NU' => 'Niue', + 'NZ' => 'New Zealand', + 'OM' => 'Oman', + 'PA' => 'Panama', + 'PE' => 'Peru', + 'PF' => 'French Polynesia', + 'PG' => 'Papua New Guinea', + 'PH' => 'Philippines', + 'PK' => 'Pakistan', + 'PL' => 'Poland', + 'PM' => 'Saint Pierre and Miquelon', + 'PN' => 'Pitcairn', + 'PR' => 'Puerto Rico', + 'PS' => 'Palestine, State of', + 'PT' => 'Portugal', + 'PW' => 'Palau', + 'PY' => 'Paraguay', + 'QA' => 'Qatar', + 'RE' => 'Réunion', + 'RO' => 'Romania', + 'RS' => 'Serbia', + 'RU' => 'Russian Federation', + 'RW' => 'Rwanda', + 'SA' => 'Saudi Arabia', + 'SB' => 'Solomon Islands', + 'SC' => 'Seychelles', + 'SD' => 'Sudan', + 'SE' => 'Sweden', + 'SG' => 'Singapore', + 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', + 'SI' => 'Slovenia', + 'SJ' => 'Svalbard and Jan Mayen', + 'SK' => 'Slovakia', + 'SL' => 'Sierra Leone', + 'SM' => 'San Marino', + 'SN' => 'Senegal', + 'SO' => 'Somalia', + 'SR' => 'Suriname', + 'SS' => 'South Sudan', + 'ST' => 'Sao Tome and Principe', + 'SV' => 'El Salvador', + 'SX' => 'Sint Maarten (Dutch part)', + 'SY' => 'Syrian Arab Republic', + 'SZ' => 'Swaziland', + 'TC' => 'Turks and Caicos Islands', + 'TD' => 'Chad', + 'TF' => 'French Southern Territories', + 'TG' => 'Togo', + 'TH' => 'Thailand', + 'TJ' => 'Tajikistan', + 'TK' => 'Tokelau', + 'TL' => 'Timor-Leste', + 'TM' => 'Turkmenistan', + 'TN' => 'Tunisia', + 'TO' => 'Tonga', + 'TR' => 'Turkey', + 'TT' => 'Trinidad and Tobago', + 'TV' => 'Tuvalu', + 'TW' => 'Taiwan, Province of China', + 'TZ' => 'Tanzania, United Republic of', + 'UA' => 'Ukraine', + 'UG' => 'Uganda', + 'UM' => 'United States Minor Outlying Islands', + 'US' => 'United States', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VA' => 'Holy See (Vatican City State)', + 'VC' => 'Saint Vincent and the Grenadines', + 'VE' => 'Venezuela, Bolivarian Republic of', + 'VG' => 'Virgin Islands, British', + 'VI' => 'Virgin Islands, U.S.', + 'VN' => 'Viet Nam', + 'VU' => 'Vanuatu', + 'WF' => 'Wallis and Futuna', + 'WS' => 'Samoa', + 'YE' => 'Yemen', + 'YT' => 'Mayotte', + 'ZA' => 'South Africa', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', +]; diff --git a/modules/Admin/Language/kk/Dashboard.php b/modules/Admin/Language/kk/Dashboard.php new file mode 100644 index 00000000..881073fd --- /dev/null +++ b/modules/Admin/Language/kk/Dashboard.php @@ -0,0 +1,28 @@ + 'Admin dashboard', + 'welcome_message' => 'Welcome to the admin area!', + 'podcasts' => [ + 'title' => 'Podcasts', + 'not_found' => 'No published podcast', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'episodes' => [ + 'title' => 'Episodes', + 'not_found' => 'No published episode', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'storage' => [ + 'title' => 'Storage', + 'subtitle' => '{totalUploaded} out of {totalStorage}', + ], +]; diff --git a/modules/Admin/Language/kk/Episode.php b/modules/Admin/Language/kk/Episode.php new file mode 100644 index 00000000..4fa846e3 --- /dev/null +++ b/modules/Admin/Language/kk/Episode.php @@ -0,0 +1,225 @@ + 'Season {seasonNumber}', + 'season_abbr' => 'S{seasonNumber}', + 'number' => 'Episode {episodeNumber}', + 'number_abbr' => 'Ep. {episodeNumber}', + 'season_episode' => 'Season {seasonNumber} episode {episodeNumber}', + 'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}', + 'number_of_comments' => '{numberOfComments, plural, + one {# comment} + other {# comments} + }', + 'all_podcast_episodes' => 'All podcast episodes', + 'back_to_podcast' => 'Go back to podcast', + 'edit' => 'Edit', + 'preview' => 'Preview', + 'publish' => 'Publish', + 'publish_edit' => 'Edit publication', + 'publish_date_edit' => 'Edit publication date', + 'unpublish' => 'Unpublish', + 'publish_error' => 'Episode is already published.', + 'publish_edit_error' => 'Episode is already published.', + 'publish_cancel_error' => 'Episode is already published.', + 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', + 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', + 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'unpublish_error' => 'Episode is not published.', + 'delete' => 'Delete', + 'go_to_page' => 'Go to page', + 'create' => 'Add an episode', + 'publication_status' => [ + 'published' => 'Published', + 'with_podcast' => 'Published', + 'scheduled' => 'Scheduled', + 'not_published' => 'Not published', + ], + 'with_podcast_hint' => 'To be published at the same time as the podcast', + 'list' => [ + 'search' => [ + 'placeholder' => 'Search for an episode', + 'clear' => 'Clear search', + 'submit' => 'Search', + ], + 'number_of_episodes' => '{numberOfEpisodes, plural, + one {# episode} + other {# episodes} + }', + 'episode' => 'Episode', + 'visibility' => 'Visibility', + 'downloads' => 'Downloads', + 'comments' => 'Comments', + 'actions' => 'Actions', + ], + 'messages' => [ + 'createSuccess' => 'Episode has been successfully created!', + 'editSuccess' => 'Episode has been successfully updated!', + 'publishSuccess' => '{publication_status, select, + published {Episode successfully published!} + scheduled {Episode publication successfully scheduled!} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not published.} + }', + 'publishCancelSuccess' => 'Episode publication successfully cancelled!', + 'unpublishBeforeDeleteTip' => 'You must unpublish the episode before deleting it.', + 'scheduleDateError' => 'Schedule date must be set!', + 'deletePublishedEpisodeError' => 'Please unpublish the episode before deleting it.', + 'deleteSuccess' => 'Episode successfully deleted!', + 'deleteError' => 'Failed to delete episode {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deleteFileError' => 'Failed to delete {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + } file {file_key}. You may manually remove it from your disk.', + 'sameSlugError' => 'An episode with the chosen slug already exists.', + ], + 'form' => [ + 'file_size_error' => + 'Your file size is too big! Max size is {0}. Increase the `memory_limit`, `upload_max_filesize` and `post_max_size` values in your php configuration file then restart your web server to upload your file.', + 'audio_file' => 'Audio file', + 'audio_file_hint' => 'Choose an .mp3 or .m4a audio file.', + 'info_section_title' => 'Episode info', + 'cover' => 'Episode cover', + 'cover_hint' => + 'If you do not set a cover, the podcast cover will be used instead.', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'title' => 'Title', + 'title_hint' => + 'Should contain a clear and concise episode name. Do not specify the episode or season numbers here.', + 'permalink' => 'Permalink', + 'season_number' => 'Season', + 'episode_number' => 'Episode', + 'type' => [ + 'label' => 'Type', + 'full' => 'Full', + 'full_hint' => 'Complete content (the episode)', + 'trailer' => 'Trailer', + 'trailer_hint' => 'Short, promotional piece of content that represents a preview of the current show', + 'bonus' => 'Bonus', + 'bonus_hint' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show', + ], + 'premium_title' => 'Premium', + 'premium' => 'Episode must be accessible to premium subscribers only', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does the episode contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'show_notes_section_title' => 'Show notes', + 'show_notes_section_subtitle' => + 'Up to 4000 characters, be clear and concise. Show notes help potential listeners in finding the episode.', + 'description' => 'Description', + 'description_footer' => 'Description footer', + 'description_footer_hint' => + 'This text is added at the end of each episode description, it is a good place to input your social links for example.', + 'additional_files_section_title' => 'Additional files', + 'additional_files_section_subtitle' => + 'These files may be used by other platforms to provide better experience to your audience. See the {podcastNamespaceLink} for more information.', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this episode about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real or fictional location', + 'transcript' => 'Transcript (subtitles / closed captions)', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', + 'transcript_download' => 'Download transcript', + 'transcript_file' => 'Transcript file (.srt or .vtt)', + 'transcript_remote_url' => 'Remote url for transcript', + 'transcript_file_delete' => 'Delete transcript file', + 'chapters' => 'Chapters', + 'chapters_hint' => 'File must be in JSON Chapters format.', + 'chapters_download' => 'Download chapters', + 'chapters_file' => 'Chapters file', + 'chapters_remote_url' => 'Remote url for chapters file', + 'chapters_file_delete' => 'Delete chapters file', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the episode', + 'custom_rss_hint' => 'This will be injected within the ❬item❭ tag.', + 'block' => 'Episode should be hidden from public catalogues', + 'block_hint' => + 'The episode show or hide status: toggling this on prevents the episode from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'submit_create' => 'Create episode', + 'submit_edit' => 'Save episode', + ], + 'publish_form' => [ + 'back_to_episode_dashboard' => 'Back to episode dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your episode. The message will be broadcasted to all your followers in the fediverse and be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + 'with_podcast' => 'Publish alongside podcast', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_clear' => 'Clear publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the episode release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit' => 'Publish', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your episode.', + 'message_warning_submit' => 'Publish anyways', + ], + 'publish_date_edit_form' => [ + 'new_publication_date' => 'New publication date', + 'new_publication_date_hint' => 'Must be set to a past date.', + 'submit' => 'Edit publication date', + ], + 'unpublish_form' => [ + 'disclaimer' => + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", + 'understand' => 'I understand, I want to unpublish the episode', + 'submit' => 'Unpublish', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.", + 'understand' => 'I understand, I want to delete the episode', + 'submit' => 'Delete', + ], + 'embed' => [ + 'title' => 'Embeddable player', + 'label' => + 'Pick a theme color, copy the embeddable player to clipboard, then paste it on your website.', + 'clipboard_iframe' => 'Copy embeddable player to clipboard', + 'clipboard_url' => 'Copy address to clipboard', + 'dark' => 'Dark', + 'dark-transparent' => 'Dark transparent', + 'light' => 'Light', + 'light-transparent' => 'Light transparent', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], +]; diff --git a/modules/Admin/Language/kk/EpisodeNavigation.php b/modules/Admin/Language/kk/EpisodeNavigation.php new file mode 100644 index 00000000..1406e301 --- /dev/null +++ b/modules/Admin/Language/kk/EpisodeNavigation.php @@ -0,0 +1,23 @@ + 'View episode page', + 'dashboard' => 'Episode dashboard', + 'episode-view' => 'Home', + 'episode-edit' => 'Edit episode', + 'episode-persons-manage' => 'Manage persons', + 'embed-add' => 'Embeddable player', + 'clips' => 'Clips', + 'video-clips-list' => 'Video clips', + 'video-clips-create' => 'New video clip', + 'soundbites-list' => 'Soundbites', + 'soundbites-create' => 'New soundbite', +]; diff --git a/modules/Admin/Language/kk/Fediverse.php b/modules/Admin/Language/kk/Fediverse.php new file mode 100644 index 00000000..0e4ca66d --- /dev/null +++ b/modules/Admin/Language/kk/Fediverse.php @@ -0,0 +1,32 @@ + [ + 'actorNotFound' => 'The account could not be found!', + 'blockActorSuccess' => '{actor} has been blocked!', + 'unblockActorSuccess' => 'Actor has been unblocked!', + 'blockDomainSuccess' => '{domain} has been blocked!', + 'unblockDomainSuccess' => '{domain} has been unblocked!', + ], + 'blocked_actors' => 'Blocked accounts', + 'blocked_domains' => 'Blocked domains', + 'block_lists_form' => [ + 'handle' => 'Account handle', + 'handle_hint' => 'Input @username@domain account.', + 'domain' => 'Domain name', + 'submit' => 'Block!', + ], + 'list' => [ + 'actor' => 'Account', + 'domain' => 'Domain name', + 'unblock' => 'Unblock', + ], +]; diff --git a/modules/Admin/Language/kk/Home.php b/modules/Admin/Language/kk/Home.php new file mode 100644 index 00000000..3ff4c04d --- /dev/null +++ b/modules/Admin/Language/kk/Home.php @@ -0,0 +1,14 @@ + 'All podcasts', + 'no_podcast' => 'No podcast found', +]; diff --git a/modules/Admin/Language/kk/Install.php b/modules/Admin/Language/kk/Install.php new file mode 100644 index 00000000..36e373a2 --- /dev/null +++ b/modules/Admin/Language/kk/Install.php @@ -0,0 +1,61 @@ + 'Manual configuration', + 'manual_config_subtitle' => + 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'form' => [ + 'instance_config' => 'Instance configuration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', + 'db_hostname' => 'Database hostname', + 'db_name' => 'Database name', + 'db_username' => 'Database username', + 'db_password' => 'Database password', + 'db_prefix' => 'Database prefix', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Cache handler', + 'cacheHandlerOptions' => [ + 'file' => 'File', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Next', + 'submit' => 'Finish install', + 'create_superadmin' => 'Create your superadmin account', + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Your superadmin account has been created successfully. Login to start podcasting!', + 'databaseConnectError' => + 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'writeError' => + "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + ], +]; diff --git a/modules/Admin/Language/kk/Navigation.php b/modules/Admin/Language/kk/Navigation.php new file mode 100644 index 00000000..f3ffb129 --- /dev/null +++ b/modules/Admin/Language/kk/Navigation.php @@ -0,0 +1,44 @@ + 'Toggle sidebar', + 'go_to_website' => 'Go to website', + 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', + 'dashboard' => 'Dashboard', + 'admin' => 'Home', + 'podcasts' => 'Podcasts', + 'podcast-list' => 'All podcasts', + 'podcast-create' => 'New podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', + 'persons' => 'Persons', + 'person-list' => 'All persons', + 'person-create' => 'New person', + 'fediverse' => 'Fediverse', + 'fediverse-blocked-actors' => 'Blocked accounts', + 'fediverse-blocked-domains' => 'Blocked domains', + 'users' => 'Users', + 'user-list' => 'All users', + 'user-create' => 'New user', + 'pages' => 'Pages', + 'page-list' => 'All pages', + 'page-create' => 'New Page', + 'settings' => 'Settings', + 'settings-general' => 'General', + 'settings-theme' => 'Theme', + 'admin-about' => 'About', + 'account' => [ + 'my-account' => 'My account', + 'change-password' => 'Change password', + 'logout' => 'Logout', + ], +]; diff --git a/modules/Admin/Language/kk/Notifications.php b/modules/Admin/Language/kk/Notifications.php new file mode 100644 index 00000000..2b139d51 --- /dev/null +++ b/modules/Admin/Language/kk/Notifications.php @@ -0,0 +1,19 @@ + 'Notifications', + 'reply' => '{actor_username} replied to your post', + 'favourite' => '{actor_username} favourited your post', + 'reblog' => '{actor_username} shared your post', + 'follow' => '{actor_username} started following you', + 'no_notifications' => 'No notifications', + 'mark_all_as_read' => 'Mark all as read', +]; diff --git a/modules/Admin/Language/kk/Page.php b/modules/Admin/Language/kk/Page.php new file mode 100644 index 00000000..b6f49de5 --- /dev/null +++ b/modules/Admin/Language/kk/Page.php @@ -0,0 +1,30 @@ + 'Back to home', + 'page' => 'Page', + 'all_pages' => 'All pages', + 'create' => 'New page', + 'go_to_page' => 'Go to page', + 'edit' => 'Edit page', + 'delete' => 'Delete page', + 'form' => [ + 'title' => 'Title', + 'permalink' => 'Permalink', + 'content' => 'Content', + 'submit_create' => 'Create page', + 'submit_edit' => 'Save', + ], + 'messages' => [ + 'createSuccess' => 'The page “{pageTitle}” was created successfully!', + 'editSuccess' => 'The page was successfully updated!', + ], +]; diff --git a/modules/Admin/Language/kk/Pager.php b/modules/Admin/Language/kk/Pager.php new file mode 100644 index 00000000..e25ee638 --- /dev/null +++ b/modules/Admin/Language/kk/Pager.php @@ -0,0 +1,21 @@ + 'Page navigation', + 'first' => 'First', + 'previous' => 'Previous', + 'next' => 'Next', + 'last' => 'Last', + 'older' => 'Older', + 'newer' => 'Newer', + 'invalidTemplate' => '{0} is not a valid Pager template.', + 'invalidPaginationGroup' => '{0} is not a valid Pagination group.', +]; diff --git a/modules/Admin/Language/kk/Person.php b/modules/Admin/Language/kk/Person.php new file mode 100644 index 00000000..a652be9f --- /dev/null +++ b/modules/Admin/Language/kk/Person.php @@ -0,0 +1,65 @@ + 'Persons', + 'all_persons' => 'All persons', + 'no_person' => 'Nobody found!', + 'create' => 'Create a person', + 'view' => 'View person', + 'edit' => 'Edit person', + 'delete' => 'Delete person', + 'messages' => [ + 'createSuccess' => 'Person has been successfully created!', + 'editSuccess' => 'Person has been successfully updated!', + 'deleteSuccess' => 'Person has been removed!', + ], + 'form' => [ + 'avatar' => 'Avatar', + 'avatar_size_hint' => + 'Avatar must be squared and at least 400px wide and tall.', + 'full_name' => 'Full name', + 'full_name_hint' => 'This is the full name or alias of the person.', + 'unique_name' => 'Unique name', + 'unique_name_hint' => 'Used for URLs', + 'information_url' => 'Information URL', + 'information_url_hint' => + 'Url to a relevant resource of information about the person, such as a homepage or third-party profile platform.', + 'submit_create' => 'Create person', + 'submit_edit' => 'Save person', + ], + 'podcast_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this podcast', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'episode_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this episode', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'credits' => 'Credits', +]; diff --git a/modules/Admin/Language/kk/Platforms.php b/modules/Admin/Language/kk/Platforms.php new file mode 100644 index 00000000..e161181c --- /dev/null +++ b/modules/Admin/Language/kk/Platforms.php @@ -0,0 +1,43 @@ + [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', + 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', + 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', + 'visible' => 'Display in podcast homepage?', + 'on_embed' => 'Display on embeddable player?', + 'remove' => 'Remove {platformName}', + 'submit' => 'Save', + 'messages' => [ + 'updateSuccess' => 'Platform links have been successfully updated!', + 'removeLinkSuccess' => 'The platform link has been removed.', + 'removeLinkError' => + 'The platform link could not be removed. Try again.', + ], + 'description' => [ + 'podcasting' => 'The podcast ID on this platform', + 'social' => 'The podcast account ID on this platform', + 'funding' => 'Call to action message', + ], +]; diff --git a/modules/Admin/Language/kk/Podcast.php b/modules/Admin/Language/kk/Podcast.php new file mode 100644 index 00000000..ff0daebc --- /dev/null +++ b/modules/Admin/Language/kk/Podcast.php @@ -0,0 +1,330 @@ + 'All podcasts', + 'no_podcast' => 'No podcast found!', + 'create' => 'Create podcast', + 'import' => 'Import podcast', + 'all_imports' => 'Podcast imports', + 'new_episode' => 'New Episode', + 'view' => 'View podcast', + 'edit' => 'Edit podcast', + 'publish' => 'Publish podcast', + 'publish_edit' => 'Edit publication', + 'delete' => 'Delete podcast', + 'see_episodes' => 'See episodes', + 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', + 'go_to_page' => 'Go to page', + 'latest_episodes' => 'Latest episodes', + 'see_all_episodes' => 'See all episodes', + 'draft' => 'Draft', + 'messages' => [ + 'createSuccess' => 'Podcast successfully created!', + 'editSuccess' => 'Podcast has been successfully updated!', + 'importSuccess' => 'Podcast has been successfully imported!', + 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', + 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, + cover {cover} + banner {banner} + other {media} + }.', + 'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.', + 'podcastFeedUpdateSuccess' => 'Successful update: {number_of_new_episodes, plural, + one {# episode was} + other {# episodes were} + } added to the podcast!', + 'podcastFeedUpToDate' => 'Podcast is already up to date.', + 'publishError' => 'This podcast is either already published or scheduled for publication.', + 'publishEditError' => 'This podcast is not scheduled for publication.', + 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', + 'scheduleDateError' => 'Schedule date must be set!', + ], + 'form' => [ + 'identity_section_title' => 'Podcast identity', + 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + + 'cover' => 'Podcast cover', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'banner' => 'Podcast banner', + 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', + 'banner_delete' => 'Delete podcast banner', + 'title' => 'Title', + 'handle' => 'Handle', + 'handle_hint' => + 'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.', + 'type' => [ + 'label' => 'Type', + 'episodic' => 'Episodic', + 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', + 'serial' => 'Serial', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + ], + 'description' => 'Description', + 'classification_section_title' => 'Classification', + 'classification_section_subtitle' => + 'These fields will impact your audience and competition.', + 'language' => 'Language', + 'category' => 'Category', + 'category_placeholder' => 'Select a category…', + 'other_categories' => 'Other categories', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does it contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'author_section_title' => 'Author', + 'author_section_subtitle' => 'Who is managing the podcast?', + 'owner_name' => 'Owner name', + 'owner_name_hint' => + 'For administrative use only. Visible in the public RSS feed.', + 'owner_email' => 'Owner email', + 'owner_email_hint' => + 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', + 'publisher' => 'Publisher', + 'publisher_hint' => + 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', + 'copyright' => 'Copyright', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this podcast about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real place or fictional', + 'monetization_section_title' => 'Monetization', + 'monetization_section_subtitle' => + 'Earn money thanks to your audience.', + 'premium' => 'Premium', + 'premium_by_default' => 'Episodes must be set as premium by default', + 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', + 'payment_pointer' => 'Payment Pointer for Web Monetization', + 'payment_pointer_hint' => + 'This is your where you will receive money thanks to Web Monetization', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the podcast', + 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', + 'new_feed_url' => 'New feed URL', + 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', + 'old_feed_url' => 'Old feed URL', + 'partnership' => 'Partnership', + 'partner_id' => 'ID', + 'partner_link_url' => 'Link URL', + 'partner_image_url' => 'Image URL', + 'partner_id_hint' => 'Your own partner ID', + 'partner_link_url_hint' => 'The generic partner link address', + 'partner_image_url_hint' => 'The generic partner image address', + 'block' => 'Podcast should be hidden from public catalogues', + 'block_hint' => + 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'complete' => 'Podcast will not be having new episodes', + 'lock' => 'Prevent podcast from being copied', + 'lock_hint' => + 'The purpose is to tell other podcast platforms whether they are allowed to import this feed. A value of yes means that any attempt to import this feed into a new platform should be rejected.', + 'submit_create' => 'Create podcast', + 'submit_edit' => 'Save podcast', + ], + 'category_options' => [ + 'uncategorized' => 'uncategorized', + 'arts' => 'Arts', + 'business' => 'Business', + 'comedy' => 'Comedy', + 'education' => 'Education', + 'fiction' => 'Fiction', + 'government' => 'Government', + 'health_and_fitness' => 'Health & Fitness', + 'history' => 'History', + 'kids_and_family' => 'Kids & Family', + 'leisure' => 'Leisure', + 'music' => 'Music', + 'news' => 'News', + 'religion_and_spirituality' => 'Religion & Spirituality', + 'science' => 'Science', + 'society_and_culture' => 'Society & Culture', + 'sports' => 'Sports', + 'technology' => 'Technology', + 'true_crime' => 'True Crime', + 'tv_and_film' => 'TV & Film', + 'books' => 'Books', + 'design' => 'Design', + 'fashion_and_beauty' => 'Fashion & Beauty', + 'food' => 'Food', + 'performing_arts' => 'Performing Arts', + 'visual_arts' => 'Visual Arts', + 'careers' => 'Careers', + 'entrepreneurship' => 'Entrepreneurship', + 'investing' => 'Investing', + 'management' => 'Management', + 'marketing' => 'Marketing', + 'non_profit' => 'Non-Profit', + 'comedy_interviews' => 'Comedy Interviews', + 'improv' => 'Improv', + 'stand_up' => 'Stand-Up', + 'courses' => 'Courses', + 'how_to' => 'How To', + 'language_learning' => 'Language Learning', + 'self_improvement' => 'Self-Improvement', + 'comedy_fiction' => 'Comedy Fiction', + 'drama' => 'Drama', + 'science_fiction' => 'Science Fiction', + 'alternative_health' => 'Alternative Health', + 'fitness' => 'Fitness', + 'medicine' => 'Medicine', + 'mental_health' => 'Mental Health', + 'nutrition' => 'Nutrition', + 'sexuality' => 'Sexuality', + 'education_for_kids' => 'Education for Kids', + 'parenting' => 'Parenting', + 'pets_and_animals' => 'Pets & Animals', + 'stories_for_kids' => 'Stories for Kids', + 'animation_and_manga' => 'Animation & Manga', + 'automotive' => 'Automotive', + 'aviation' => 'Aviation', + 'crafts' => 'Crafts', + 'games' => 'Games', + 'hobbies' => 'Hobbies', + 'home_and_garden' => 'Home & Garden', + 'video_games' => 'Video Games', + 'music_commentary' => 'Music Commentary', + 'music_history' => 'Music History', + 'music_interviews' => 'Music Interviews', + 'business_news' => 'Business News', + 'daily_news' => 'Daily News', + 'entertainment_news' => 'Entertainment News', + 'news_commentary' => 'News Commentary', + 'politics' => 'Politics', + 'sports_news' => 'Sports News', + 'tech_news' => 'Tech News', + 'buddhism' => 'Buddhism', + 'christianity' => 'Christianity', + 'hinduism' => 'Hinduism', + 'islam' => 'Islam', + 'judaism' => 'Judaism', + 'religion' => 'Religion', + 'spirituality' => 'Spirituality', + 'astronomy' => 'Astronomy', + 'chemistry' => 'Chemistry', + 'earth_sciences' => 'Earth Sciences', + 'life_sciences' => 'Life Sciences', + 'mathematics' => 'Mathematics', + 'natural_sciences' => 'Natural Sciences', + 'nature' => 'Nature', + 'physics' => 'Physics', + 'social_sciences' => 'Social Sciences', + 'documentary' => 'Documentary', + 'personal_journals' => 'Personal Journals', + 'philosophy' => 'Philosophy', + 'places_and_travel' => 'Places & Travel', + 'relationships' => 'Relationships', + 'baseball' => 'Baseball', + 'basketball' => 'Basketball', + 'cricket' => 'Cricket', + 'fantasy_sports' => 'Fantasy Sports', + 'football' => 'Football', + 'golf' => 'Golf', + 'hockey' => 'Hockey', + 'rugby' => 'Rugby', + 'running' => 'Running', + 'soccer' => 'Soccer', + 'swimming' => 'Swimming', + 'tennis' => 'Tennis', + 'volleyball' => 'Volleyball', + 'wilderness' => 'Wilderness', + 'wrestling' => 'Wrestling', + 'after_shows' => 'After Shows', + 'film_history' => 'Film History', + 'film_interviews' => 'Film Interviews', + 'film_reviews' => 'Film Reviews', + 'tv_reviews' => 'TV Reviews', + ], + 'publish_form' => [ + 'back_to_podcast_dashboard' => 'Back to podcast dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'submit' => 'Publish', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', + 'message_warning_submit' => 'Publish anyway', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'not_published' => 'This podcast is not yet published.', + 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", + 'understand' => 'I understand, I want the podcast to be permanently deleted', + 'submit' => 'Delete', + ], + 'by' => 'By {publisher}', + 'season' => 'Season {seasonNumber}', + 'list_of_episodes_year' => '{year} episodes ({episodeCount})', + 'list_of_episodes_season' => + 'Season {seasonNumber} episodes ({episodeCount})', + 'no_episode' => 'No episode found!', + 'follow' => 'Follow', + 'followers' => '{numberOfFollowers, plural, + one {# follower} + other {# followers} + }', + 'posts' => '{numberOfPosts, plural, + one {# post} + other {# posts} + }', + 'activity' => 'Activity', + 'episodes' => 'Episodes', + 'sponsor' => 'Sponsor', + 'funding_links' => 'Funding links for {podcastTitle}', + 'find_on' => 'Find {podcastTitle} on', + 'listen_on' => 'Listen on', +]; diff --git a/modules/Admin/Language/kk/PodcastNavigation.php b/modules/Admin/Language/kk/PodcastNavigation.php new file mode 100644 index 00000000..bb777707 --- /dev/null +++ b/modules/Admin/Language/kk/PodcastNavigation.php @@ -0,0 +1,42 @@ + 'Go to podcast page', + 'rss_feed' => 'RSS feed', + 'dashboard' => 'Podcast dashboard', + 'podcast-view' => 'Home', + 'podcast-edit' => 'Edit podcast', + 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', + 'episodes' => 'Episodes', + 'episode-list' => 'All episodes', + 'episode-create' => 'New episode', + 'analytics' => 'Analytics', + 'podcast-analytics' => 'Audience overview', + 'podcast-analytics-webpages' => 'Web pages visits', + 'podcast-analytics-locations' => 'Locations', + 'podcast-analytics-unique-listeners' => 'Unique listeners', + 'podcast-analytics-players' => 'Players', + 'podcast-analytics-listening-time' => 'Listening time', + 'podcast-analytics-time-periods' => 'Time periods', + 'monetization' => 'Monetization', + 'subscription-list' => 'All subscriptions', + 'subscription-create' => 'Add subscription', + 'contributors' => 'Contributors', + 'contributor-list' => 'All contributors', + 'contributor-add' => 'Add contributor', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', + 'platforms-social' => 'Social networks', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', +]; diff --git a/modules/Admin/Language/kk/Settings.php b/modules/Admin/Language/kk/Settings.php new file mode 100644 index 00000000..4a70dcba --- /dev/null +++ b/modules/Admin/Language/kk/Settings.php @@ -0,0 +1,58 @@ + 'General settings', + 'instance' => [ + 'title' => 'Instance', + 'site_icon' => 'Site icon', + 'site_icon_delete' => 'Delete site icon', + 'site_icon_hint' => 'Site icons are what you see on your browser tabs, bookmarks bar, and when you add a website as a shortcut on mobile devices.', + 'site_icon_helper' => 'Icon must be squared and at least 512px wide and tall.', + 'site_name' => 'Site name', + 'site_description' => 'Site description', + 'submit' => 'Save', + 'editSuccess' => 'Instance has been updated successfully!', + 'deleteIconSuccess' => 'Site icon has been remove successfully!', + ], + 'images' => [ + 'title' => 'Images', + 'subtitle' => 'Here you can regenerate all images based on the originals that were uploaded. To be used if you find that some images are missing. This task may take a while.', + 'regenerate' => 'Regenerate images', + 'regenerationSuccess' => 'All images have been regenerated successfully!', + ], + 'housekeeping' => [ + 'title' => 'Housekeeping', + 'subtitle' => 'Runs various housekeeping tasks. Use this feature if you ever encounter issues with media files or data integrity. These tasks may take a while.', + 'reset_counts' => 'Reset counts', + 'reset_counts_helper' => 'This option will recalculate and reset all data counts (number of followers, posts, comments, …).', + 'rewrite_media' => 'Rewrite media metadata', + 'rewrite_media_helper' => 'This option will delete all superfluous media files and recreate them (images, audio files, transcripts, chapters, …)', + 'rename_episodes_files' => 'Rename episode audio files', + 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', + 'clear_cache' => 'Clear all cache', + 'clear_cache_helper' => 'This option will flush redis cache or writable/cache files.', + 'run' => 'Run housekeeping', + 'runSuccess' => 'Housekeeping has been run successfully!', + ], + 'theme' => [ + 'title' => 'Theme', + 'accent_section_title' => 'Accent color', + 'accent_section_subtitle' => 'Choose the color to determine the look and feel of all public pages.', + 'pine' => 'Pine', + 'crimson' => 'Crimson', + 'amber' => 'Amber', + 'lake' => 'Lake', + 'jacaranda' => 'Jacaranda', + 'onyx' => 'Onyx', + 'submit' => 'Save', + 'setInstanceThemeSuccess' => 'Theme has been updated successfully!', + ], +]; diff --git a/modules/Admin/Language/kk/Soundbite.php b/modules/Admin/Language/kk/Soundbite.php new file mode 100644 index 00000000..a3f828fe --- /dev/null +++ b/modules/Admin/Language/kk/Soundbite.php @@ -0,0 +1,31 @@ + [ + 'title' => 'Soundbites', + 'soundbite' => 'Soundbite', + ], + 'messages' => [ + 'createSuccess' => 'Soundbite has been successfully created!', + 'deleteSuccess' => 'Soundbite has been successfully removed!', + ], + 'form' => [ + 'title' => 'New soundbite', + 'soundbite_title' => 'Soundbite title', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'submit' => 'Create soundbite', + ], + 'play' => 'Play soundbite', + 'stop' => 'Stop soundbite', + 'create' => 'New soundbite', + 'delete' => 'Delete soundbite', +]; diff --git a/modules/Admin/Language/kk/Validation.php b/modules/Admin/Language/kk/Validation.php new file mode 100644 index 00000000..f76c3163 --- /dev/null +++ b/modules/Admin/Language/kk/Validation.php @@ -0,0 +1,17 @@ + + '{field} is either not an image, or it is not wide or tall enough.', + 'is_image_ratio' => + '{field} is either not an image or not of the right ratio.', + 'is_json' => '{field} contains invalid JSON.', +]; diff --git a/modules/Admin/Language/kk/VideoClip.php b/modules/Admin/Language/kk/VideoClip.php new file mode 100644 index 00000000..638de697 --- /dev/null +++ b/modules/Admin/Language/kk/VideoClip.php @@ -0,0 +1,72 @@ + [ + 'title' => 'Video clips', + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Clip is waiting to be processed.', + 'pending' => 'pending', + 'pending_hint' => 'Clip will be generated shortly.', + 'running' => 'running', + 'running_hint' => 'Clip is being generated.', + 'failed' => 'failed', + 'failed_hint' => 'Clip could not be generated: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Clip was generated successfully!', + ], + 'clip' => 'Clip', + 'duration' => 'Job duration', + ], + 'title' => 'Video clip: {videoClipLabel}', + 'download_clip' => 'Download clip', + 'create' => 'New video clip', + 'go_to_page' => 'Go to clip page', + 'retry' => 'Retry clip generation', + 'delete' => 'Delete clip', + 'logs' => 'Job logs', + 'messages' => [ + 'alreadyExistingError' => 'The video clip you are trying to create already exists!', + 'addToQueueSuccess' => 'Video clip has been added to queue, awaiting to be created!', + 'deleteSuccess' => 'Video clip has been successfully removed!', + ], + 'format' => [ + 'landscape' => 'Landscape', + 'portrait' => 'Portrait', + 'squared' => 'Squared', + ], + 'form' => [ + 'title' => 'New video clip', + 'params_section_title' => 'Video clip parameters', + 'clip_title' => 'Clip title', + 'format' => [ + 'label' => 'Choose a format', + 'landscape_hint' => 'With a 16:9 ratio, landscape videos are great for PeerTube, Youtube and Vimeo.', + 'portrait_hint' => 'With a 9:16 ratio, portrait videos are great for TikTok, Youtube shorts and Instagram stories.', + 'squared_hint' => 'With a 1:1 ratio, squared videos are great for Mastodon, Facebook, Twitter and LinkedIn.', + ], + 'theme' => 'Select a theme', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'trim_start' => 'Trim start', + 'trim_end' => 'Trim end', + 'submit' => 'Create video clip', + ], + 'requirements' => [ + 'title' => 'Missing requirements', + 'missing' => 'You have missing requirements. Make sure to add all the required items to be allowed creating a video for this episode!', + 'ffmpeg' => 'FFmpeg', + 'gd' => 'Graphics Draw (GD)', + 'freetype' => 'Freetype library for GD', + 'transcript' => 'Transcript file (.srt)', + ], +]; diff --git a/modules/Admin/Language/ko/AboutCastopod.php b/modules/Admin/Language/ko/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/ko/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/ko/Breadcrumb.php b/modules/Admin/Language/ko/Breadcrumb.php new file mode 100644 index 00000000..408c9f9f --- /dev/null +++ b/modules/Admin/Language/ko/Breadcrumb.php @@ -0,0 +1,57 @@ + 'breadcrumb', + config('Admin') + ->gateway => 'Home', + 'podcasts' => 'podcasts', + 'episodes' => 'episodes', + 'subscriptions' => 'subscriptions', + 'contributors' => 'contributors', + 'pages' => 'pages', + 'settings' => 'settings', + 'theme' => 'theme', + 'about' => 'about', + 'add' => 'add', + 'new' => 'new', + 'edit' => 'edit', + 'persons' => 'persons', + 'publish' => 'publish', + 'publish-edit' => 'edit publication', + 'publish-date-edit' => 'edit publication date', + 'unpublish' => 'unpublish', + 'delete' => 'delete', + 'remove' => 'remove', + 'fediverse' => 'fediverse', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', + 'users' => 'users', + 'my-account' => 'my account', + 'change-password' => 'change password', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', + 'platforms' => 'platforms', + 'social' => 'social networks', + 'funding' => 'funding', + 'monetization-other' => 'other monetization', + 'analytics' => 'analytics', + 'locations' => 'locations', + 'webpages' => 'web pages', + 'unique-listeners' => 'unique listeners', + 'players' => 'players', + 'listening-time' => 'listening time', + 'time-periods' => 'time periods', + 'soundbites' => 'soundbites', + 'video-clips' => 'video clips', + 'embed' => 'embeddable player', + 'notifications' => 'notifications', + 'suspend' => 'suspend', +]; diff --git a/modules/Admin/Language/ko/Charts.php b/modules/Admin/Language/ko/Charts.php new file mode 100644 index 00000000..6ede2510 --- /dev/null +++ b/modules/Admin/Language/ko/Charts.php @@ -0,0 +1,41 @@ + 'Episode downloads by service (for the past week)', + 'by_player_weekly' => 'Episode downloads by player (for the past week)', + 'by_player_yearly' => 'Episode downloads by player (for the past year)', + 'by_device_weekly' => 'Episode downloads by device (for the past week)', + 'by_os_weekly' => 'Episode downloads by O.S. (for the past week)', + 'podcast_by_region' => 'Episode downloads by region (for the past week)', + 'unique_daily_listeners' => 'Daily unique listeners', + 'unique_monthly_listeners' => 'Monthly unique listeners', + 'by_browser' => 'Web pages usage by browser (for the past week)', + 'podcast_by_day' => 'Episode daily downloads', + 'podcast_by_month' => 'Episode monthly downloads', + 'episode_by_day' => 'Episode daily downloads (first 60 days)', + 'episode_by_month' => 'Episode monthly downloads', + 'episodes_by_day' => + '5 latest episodes downloads (during their first 60 days)', + 'by_country_weekly' => 'Episode downloads by country (for the past week)', + 'by_country_yearly' => 'Episode downloads by country (for the past year)', + 'by_domain_weekly' => 'Web pages visits by source (for the past week)', + 'by_domain_yearly' => 'Web pages visits by source (for the past year)', + 'by_entry_page' => 'Web pages visits by landing page (for the past week)', + 'podcast_bots' => 'Bots (crawlers)', + 'daily_listening_time' => 'Daily cumulative listening time', + 'monthly_listening_time' => 'Monthly cumulative listening time', + 'by_weekday' => 'By week day (for the past 60 days)', + 'by_hour' => 'By time of day (for the past 60 days)', + 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', + 'total_storage_by_month' => 'Monthly storage (in MB)', + 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', +]; diff --git a/modules/Admin/Language/ko/Common.php b/modules/Admin/Language/ko/Common.php new file mode 100644 index 00000000..74addcf2 --- /dev/null +++ b/modules/Admin/Language/ko/Common.php @@ -0,0 +1,52 @@ + 'Yes', + 'no' => 'No', + 'cancel' => 'Cancel', + 'optional' => 'Optional', + 'more' => 'More', + 'no_data' => 'No data found!', + 'close' => 'Close', + 'edit' => 'Edit', + 'copy' => 'Copy', + 'copied' => 'Copied!', + 'home' => 'Home', + 'explicit' => 'Explicit', + 'powered_by' => 'Powered by {castopod}', + 'actions' => 'Actions', + 'pageInfo' => 'Page {currentPage} out of {pageCount}', + 'go_back' => 'Go back', + 'forms' => [ + 'editor' => [ + 'write' => 'Write', + 'preview' => 'Preview', + 'help' => 'Powered by markdown', + ], + 'multiSelect' => [ + 'selectText' => 'Press to select', + 'loadingText' => 'Loading…', + 'noResultsText' => 'No results found', + 'noChoicesText' => 'No choices to choose from', + 'maxItemText' => 'Cannot add more items', + ], + 'upload_file' => 'Upload a file', + 'remote_url' => 'Remote URL', + 'save' => 'Save', + ], + 'play_episode_button' => [ + 'play' => 'Play', + 'playing' => 'Playing', + ], + 'size_limit' => 'Size limit: {0}.', + 'choose_interact' => 'Choose how to interact', + 'view' => 'View', +]; diff --git a/modules/Admin/Language/ko/Countries.php b/modules/Admin/Language/ko/Countries.php new file mode 100644 index 00000000..4cd5d9c8 --- /dev/null +++ b/modules/Admin/Language/ko/Countries.php @@ -0,0 +1,264 @@ + 'Andorra', + 'AE' => 'United Arab Emirates', + 'AF' => 'Afghanistan', + 'AG' => 'Antigua and Barbuda', + 'AI' => 'Anguilla', + 'AL' => 'Albania', + 'AM' => 'Armenia', + 'AO' => 'Angola', + 'AQ' => 'Antarctica', + 'AR' => 'Argentina', + 'AS' => 'American Samoa', + 'AT' => 'Austria', + 'AU' => 'Australia', + 'AW' => 'Aruba', + 'AX' => 'Åland Islands', + 'AZ' => 'Azerbaijan', + 'BA' => 'Bosnia and Herzegovina', + 'BB' => 'Barbados', + 'BD' => 'Bangladesh', + 'BE' => 'Belgium', + 'BF' => 'Burkina Faso', + 'BG' => 'Bulgaria', + 'BH' => 'Bahrain', + 'BI' => 'Burundi', + 'BJ' => 'Benin', + 'BL' => 'Saint Barthélemy', + 'BM' => 'Bermuda', + 'BN' => 'Brunei Darussalam', + 'BO' => 'Bolivia, Plurinational State of', + 'BQ' => 'Bonaire, Sint Eustatius and Saba', + 'BR' => 'Brazil', + 'BS' => 'Bahamas', + 'BT' => 'Bhutan', + 'BV' => 'Bouvet Island', + 'BW' => 'Botswana', + 'BY' => 'Belarus', + 'BZ' => 'Belize', + 'CA' => 'Canada', + 'CC' => 'Cocos (Keeling) Islands', + 'CD' => 'Congo, the Democratic Republic of the', + 'CF' => 'Central African Republic', + 'CG' => 'Congo', + 'CH' => 'Switzerland', + 'CI' => "Côte d'Ivoire", + 'CK' => 'Cook Islands', + 'CL' => 'Chile', + 'CM' => 'Cameroon', + 'CN' => 'China', + 'CO' => 'Colombia', + 'CR' => 'Costa Rica', + 'CU' => 'Cuba', + 'CV' => 'Cape Verde', + 'CW' => 'Curaçao', + 'CX' => 'Christmas Island', + 'CY' => 'Cyprus', + 'CZ' => 'Czech Republic', + 'DE' => 'Germany', + 'DJ' => 'Djibouti', + 'DK' => 'Denmark', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'DZ' => 'Algeria', + 'EC' => 'Ecuador', + 'EE' => 'Estonia', + 'EG' => 'Egypt', + 'EH' => 'Western Sahara', + 'ER' => 'Eritrea', + 'ES' => 'Spain', + 'ET' => 'Ethiopia', + 'FI' => 'Finland', + 'FJ' => 'Fiji', + 'FK' => 'Falkland Islands (Malvinas)', + 'FM' => 'Micronesia, Federated States of', + 'FO' => 'Faroe Islands', + 'FR' => 'France', + 'GA' => 'Gabon', + 'GB' => 'United Kingdom', + 'GD' => 'Grenada', + 'GE' => 'Georgia', + 'GF' => 'French Guiana', + 'GG' => 'Guernsey', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GL' => 'Greenland', + 'GM' => 'Gambia', + 'GN' => 'Guinea', + 'GP' => 'Guadeloupe', + 'GQ' => 'Equatorial Guinea', + 'GR' => 'Greece', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'GT' => 'Guatemala', + 'GU' => 'Guam', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HK' => 'Hong Kong', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HR' => 'Croatia', + 'HT' => 'Haiti', + 'HU' => 'Hungary', + 'ID' => 'Indonesia', + 'IE' => 'Ireland', + 'IL' => 'Israel', + 'IM' => 'Isle of Man', + 'IN' => 'India', + 'IO' => 'British Indian Ocean Territory', + 'IQ' => 'Iraq', + 'IR' => 'Iran, Islamic Republic of', + 'IS' => 'Iceland', + 'IT' => 'Italy', + 'JE' => 'Jersey', + 'JM' => 'Jamaica', + 'JO' => 'Jordan', + 'JP' => 'Japan', + 'KE' => 'Kenya', + 'KG' => 'Kyrgyzstan', + 'KH' => 'Cambodia', + 'KI' => 'Kiribati', + 'KM' => 'Comoros', + 'KN' => 'Saint Kitts and Nevis', + 'KP' => "Korea, Democratic People's Republic of", + 'KR' => 'Korea, Republic of', + 'KW' => 'Kuwait', + 'KY' => 'Cayman Islands', + 'KZ' => 'Kazakhstan', + 'LA' => "Lao People's Democratic Republic", + 'LB' => 'Lebanon', + 'LC' => 'Saint Lucia', + 'LI' => 'Liechtenstein', + 'LK' => 'Sri Lanka', + 'LR' => 'Liberia', + 'LS' => 'Lesotho', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'LV' => 'Latvia', + 'LY' => 'Libya', + 'MA' => 'Morocco', + 'MC' => 'Monaco', + 'MD' => 'Moldova, Republic of', + 'ME' => 'Montenegro', + 'MF' => 'Saint Martin (French part)', + 'MG' => 'Madagascar', + 'MH' => 'Marshall Islands', + 'MK' => 'Macedonia, the Former Yugoslav Republic of', + 'ML' => 'Mali', + 'MM' => 'Myanmar', + 'MN' => 'Mongolia', + 'MO' => 'Macao', + 'MP' => 'Northern Mariana Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MS' => 'Montserrat', + 'MT' => 'Malta', + 'MU' => 'Mauritius', + 'MV' => 'Maldives', + 'MW' => 'Malawi', + 'MX' => 'Mexico', + 'MY' => 'Malaysia', + 'MZ' => 'Mozambique', + 'N/A' => 'Not Applicable (local IP…)', + 'NA' => 'Namibia', + 'NC' => 'New Caledonia', + 'NE' => 'Niger', + 'NF' => 'Norfolk Island', + 'NG' => 'Nigeria', + 'NI' => 'Nicaragua', + 'NL' => 'Netherlands', + 'NO' => 'Norway', + 'NP' => 'Nepal', + 'NR' => 'Nauru', + 'NU' => 'Niue', + 'NZ' => 'New Zealand', + 'OM' => 'Oman', + 'PA' => 'Panama', + 'PE' => 'Peru', + 'PF' => 'French Polynesia', + 'PG' => 'Papua New Guinea', + 'PH' => 'Philippines', + 'PK' => 'Pakistan', + 'PL' => 'Poland', + 'PM' => 'Saint Pierre and Miquelon', + 'PN' => 'Pitcairn', + 'PR' => 'Puerto Rico', + 'PS' => 'Palestine, State of', + 'PT' => 'Portugal', + 'PW' => 'Palau', + 'PY' => 'Paraguay', + 'QA' => 'Qatar', + 'RE' => 'Réunion', + 'RO' => 'Romania', + 'RS' => 'Serbia', + 'RU' => 'Russian Federation', + 'RW' => 'Rwanda', + 'SA' => 'Saudi Arabia', + 'SB' => 'Solomon Islands', + 'SC' => 'Seychelles', + 'SD' => 'Sudan', + 'SE' => 'Sweden', + 'SG' => 'Singapore', + 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', + 'SI' => 'Slovenia', + 'SJ' => 'Svalbard and Jan Mayen', + 'SK' => 'Slovakia', + 'SL' => 'Sierra Leone', + 'SM' => 'San Marino', + 'SN' => 'Senegal', + 'SO' => 'Somalia', + 'SR' => 'Suriname', + 'SS' => 'South Sudan', + 'ST' => 'Sao Tome and Principe', + 'SV' => 'El Salvador', + 'SX' => 'Sint Maarten (Dutch part)', + 'SY' => 'Syrian Arab Republic', + 'SZ' => 'Swaziland', + 'TC' => 'Turks and Caicos Islands', + 'TD' => 'Chad', + 'TF' => 'French Southern Territories', + 'TG' => 'Togo', + 'TH' => 'Thailand', + 'TJ' => 'Tajikistan', + 'TK' => 'Tokelau', + 'TL' => 'Timor-Leste', + 'TM' => 'Turkmenistan', + 'TN' => 'Tunisia', + 'TO' => 'Tonga', + 'TR' => 'Turkey', + 'TT' => 'Trinidad and Tobago', + 'TV' => 'Tuvalu', + 'TW' => 'Taiwan, Province of China', + 'TZ' => 'Tanzania, United Republic of', + 'UA' => 'Ukraine', + 'UG' => 'Uganda', + 'UM' => 'United States Minor Outlying Islands', + 'US' => 'United States', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VA' => 'Holy See (Vatican City State)', + 'VC' => 'Saint Vincent and the Grenadines', + 'VE' => 'Venezuela, Bolivarian Republic of', + 'VG' => 'Virgin Islands, British', + 'VI' => 'Virgin Islands, U.S.', + 'VN' => 'Viet Nam', + 'VU' => 'Vanuatu', + 'WF' => 'Wallis and Futuna', + 'WS' => 'Samoa', + 'YE' => 'Yemen', + 'YT' => 'Mayotte', + 'ZA' => 'South Africa', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', +]; diff --git a/modules/Admin/Language/ko/Dashboard.php b/modules/Admin/Language/ko/Dashboard.php new file mode 100644 index 00000000..881073fd --- /dev/null +++ b/modules/Admin/Language/ko/Dashboard.php @@ -0,0 +1,28 @@ + 'Admin dashboard', + 'welcome_message' => 'Welcome to the admin area!', + 'podcasts' => [ + 'title' => 'Podcasts', + 'not_found' => 'No published podcast', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'episodes' => [ + 'title' => 'Episodes', + 'not_found' => 'No published episode', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'storage' => [ + 'title' => 'Storage', + 'subtitle' => '{totalUploaded} out of {totalStorage}', + ], +]; diff --git a/modules/Admin/Language/ko/Episode.php b/modules/Admin/Language/ko/Episode.php new file mode 100644 index 00000000..4fa846e3 --- /dev/null +++ b/modules/Admin/Language/ko/Episode.php @@ -0,0 +1,225 @@ + 'Season {seasonNumber}', + 'season_abbr' => 'S{seasonNumber}', + 'number' => 'Episode {episodeNumber}', + 'number_abbr' => 'Ep. {episodeNumber}', + 'season_episode' => 'Season {seasonNumber} episode {episodeNumber}', + 'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}', + 'number_of_comments' => '{numberOfComments, plural, + one {# comment} + other {# comments} + }', + 'all_podcast_episodes' => 'All podcast episodes', + 'back_to_podcast' => 'Go back to podcast', + 'edit' => 'Edit', + 'preview' => 'Preview', + 'publish' => 'Publish', + 'publish_edit' => 'Edit publication', + 'publish_date_edit' => 'Edit publication date', + 'unpublish' => 'Unpublish', + 'publish_error' => 'Episode is already published.', + 'publish_edit_error' => 'Episode is already published.', + 'publish_cancel_error' => 'Episode is already published.', + 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', + 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', + 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'unpublish_error' => 'Episode is not published.', + 'delete' => 'Delete', + 'go_to_page' => 'Go to page', + 'create' => 'Add an episode', + 'publication_status' => [ + 'published' => 'Published', + 'with_podcast' => 'Published', + 'scheduled' => 'Scheduled', + 'not_published' => 'Not published', + ], + 'with_podcast_hint' => 'To be published at the same time as the podcast', + 'list' => [ + 'search' => [ + 'placeholder' => 'Search for an episode', + 'clear' => 'Clear search', + 'submit' => 'Search', + ], + 'number_of_episodes' => '{numberOfEpisodes, plural, + one {# episode} + other {# episodes} + }', + 'episode' => 'Episode', + 'visibility' => 'Visibility', + 'downloads' => 'Downloads', + 'comments' => 'Comments', + 'actions' => 'Actions', + ], + 'messages' => [ + 'createSuccess' => 'Episode has been successfully created!', + 'editSuccess' => 'Episode has been successfully updated!', + 'publishSuccess' => '{publication_status, select, + published {Episode successfully published!} + scheduled {Episode publication successfully scheduled!} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not published.} + }', + 'publishCancelSuccess' => 'Episode publication successfully cancelled!', + 'unpublishBeforeDeleteTip' => 'You must unpublish the episode before deleting it.', + 'scheduleDateError' => 'Schedule date must be set!', + 'deletePublishedEpisodeError' => 'Please unpublish the episode before deleting it.', + 'deleteSuccess' => 'Episode successfully deleted!', + 'deleteError' => 'Failed to delete episode {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deleteFileError' => 'Failed to delete {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + } file {file_key}. You may manually remove it from your disk.', + 'sameSlugError' => 'An episode with the chosen slug already exists.', + ], + 'form' => [ + 'file_size_error' => + 'Your file size is too big! Max size is {0}. Increase the `memory_limit`, `upload_max_filesize` and `post_max_size` values in your php configuration file then restart your web server to upload your file.', + 'audio_file' => 'Audio file', + 'audio_file_hint' => 'Choose an .mp3 or .m4a audio file.', + 'info_section_title' => 'Episode info', + 'cover' => 'Episode cover', + 'cover_hint' => + 'If you do not set a cover, the podcast cover will be used instead.', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'title' => 'Title', + 'title_hint' => + 'Should contain a clear and concise episode name. Do not specify the episode or season numbers here.', + 'permalink' => 'Permalink', + 'season_number' => 'Season', + 'episode_number' => 'Episode', + 'type' => [ + 'label' => 'Type', + 'full' => 'Full', + 'full_hint' => 'Complete content (the episode)', + 'trailer' => 'Trailer', + 'trailer_hint' => 'Short, promotional piece of content that represents a preview of the current show', + 'bonus' => 'Bonus', + 'bonus_hint' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show', + ], + 'premium_title' => 'Premium', + 'premium' => 'Episode must be accessible to premium subscribers only', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does the episode contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'show_notes_section_title' => 'Show notes', + 'show_notes_section_subtitle' => + 'Up to 4000 characters, be clear and concise. Show notes help potential listeners in finding the episode.', + 'description' => 'Description', + 'description_footer' => 'Description footer', + 'description_footer_hint' => + 'This text is added at the end of each episode description, it is a good place to input your social links for example.', + 'additional_files_section_title' => 'Additional files', + 'additional_files_section_subtitle' => + 'These files may be used by other platforms to provide better experience to your audience. See the {podcastNamespaceLink} for more information.', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this episode about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real or fictional location', + 'transcript' => 'Transcript (subtitles / closed captions)', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', + 'transcript_download' => 'Download transcript', + 'transcript_file' => 'Transcript file (.srt or .vtt)', + 'transcript_remote_url' => 'Remote url for transcript', + 'transcript_file_delete' => 'Delete transcript file', + 'chapters' => 'Chapters', + 'chapters_hint' => 'File must be in JSON Chapters format.', + 'chapters_download' => 'Download chapters', + 'chapters_file' => 'Chapters file', + 'chapters_remote_url' => 'Remote url for chapters file', + 'chapters_file_delete' => 'Delete chapters file', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the episode', + 'custom_rss_hint' => 'This will be injected within the ❬item❭ tag.', + 'block' => 'Episode should be hidden from public catalogues', + 'block_hint' => + 'The episode show or hide status: toggling this on prevents the episode from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'submit_create' => 'Create episode', + 'submit_edit' => 'Save episode', + ], + 'publish_form' => [ + 'back_to_episode_dashboard' => 'Back to episode dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your episode. The message will be broadcasted to all your followers in the fediverse and be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + 'with_podcast' => 'Publish alongside podcast', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_clear' => 'Clear publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the episode release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit' => 'Publish', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your episode.', + 'message_warning_submit' => 'Publish anyways', + ], + 'publish_date_edit_form' => [ + 'new_publication_date' => 'New publication date', + 'new_publication_date_hint' => 'Must be set to a past date.', + 'submit' => 'Edit publication date', + ], + 'unpublish_form' => [ + 'disclaimer' => + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", + 'understand' => 'I understand, I want to unpublish the episode', + 'submit' => 'Unpublish', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.", + 'understand' => 'I understand, I want to delete the episode', + 'submit' => 'Delete', + ], + 'embed' => [ + 'title' => 'Embeddable player', + 'label' => + 'Pick a theme color, copy the embeddable player to clipboard, then paste it on your website.', + 'clipboard_iframe' => 'Copy embeddable player to clipboard', + 'clipboard_url' => 'Copy address to clipboard', + 'dark' => 'Dark', + 'dark-transparent' => 'Dark transparent', + 'light' => 'Light', + 'light-transparent' => 'Light transparent', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], +]; diff --git a/modules/Admin/Language/ko/EpisodeNavigation.php b/modules/Admin/Language/ko/EpisodeNavigation.php new file mode 100644 index 00000000..1406e301 --- /dev/null +++ b/modules/Admin/Language/ko/EpisodeNavigation.php @@ -0,0 +1,23 @@ + 'View episode page', + 'dashboard' => 'Episode dashboard', + 'episode-view' => 'Home', + 'episode-edit' => 'Edit episode', + 'episode-persons-manage' => 'Manage persons', + 'embed-add' => 'Embeddable player', + 'clips' => 'Clips', + 'video-clips-list' => 'Video clips', + 'video-clips-create' => 'New video clip', + 'soundbites-list' => 'Soundbites', + 'soundbites-create' => 'New soundbite', +]; diff --git a/modules/Admin/Language/ko/Fediverse.php b/modules/Admin/Language/ko/Fediverse.php new file mode 100644 index 00000000..0e4ca66d --- /dev/null +++ b/modules/Admin/Language/ko/Fediverse.php @@ -0,0 +1,32 @@ + [ + 'actorNotFound' => 'The account could not be found!', + 'blockActorSuccess' => '{actor} has been blocked!', + 'unblockActorSuccess' => 'Actor has been unblocked!', + 'blockDomainSuccess' => '{domain} has been blocked!', + 'unblockDomainSuccess' => '{domain} has been unblocked!', + ], + 'blocked_actors' => 'Blocked accounts', + 'blocked_domains' => 'Blocked domains', + 'block_lists_form' => [ + 'handle' => 'Account handle', + 'handle_hint' => 'Input @username@domain account.', + 'domain' => 'Domain name', + 'submit' => 'Block!', + ], + 'list' => [ + 'actor' => 'Account', + 'domain' => 'Domain name', + 'unblock' => 'Unblock', + ], +]; diff --git a/modules/Admin/Language/ko/Home.php b/modules/Admin/Language/ko/Home.php new file mode 100644 index 00000000..3ff4c04d --- /dev/null +++ b/modules/Admin/Language/ko/Home.php @@ -0,0 +1,14 @@ + 'All podcasts', + 'no_podcast' => 'No podcast found', +]; diff --git a/modules/Admin/Language/ko/Install.php b/modules/Admin/Language/ko/Install.php new file mode 100644 index 00000000..36e373a2 --- /dev/null +++ b/modules/Admin/Language/ko/Install.php @@ -0,0 +1,61 @@ + 'Manual configuration', + 'manual_config_subtitle' => + 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'form' => [ + 'instance_config' => 'Instance configuration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', + 'db_hostname' => 'Database hostname', + 'db_name' => 'Database name', + 'db_username' => 'Database username', + 'db_password' => 'Database password', + 'db_prefix' => 'Database prefix', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Cache handler', + 'cacheHandlerOptions' => [ + 'file' => 'File', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Next', + 'submit' => 'Finish install', + 'create_superadmin' => 'Create your superadmin account', + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Your superadmin account has been created successfully. Login to start podcasting!', + 'databaseConnectError' => + 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'writeError' => + "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + ], +]; diff --git a/modules/Admin/Language/ko/Navigation.php b/modules/Admin/Language/ko/Navigation.php new file mode 100644 index 00000000..f3ffb129 --- /dev/null +++ b/modules/Admin/Language/ko/Navigation.php @@ -0,0 +1,44 @@ + 'Toggle sidebar', + 'go_to_website' => 'Go to website', + 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', + 'dashboard' => 'Dashboard', + 'admin' => 'Home', + 'podcasts' => 'Podcasts', + 'podcast-list' => 'All podcasts', + 'podcast-create' => 'New podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', + 'persons' => 'Persons', + 'person-list' => 'All persons', + 'person-create' => 'New person', + 'fediverse' => 'Fediverse', + 'fediverse-blocked-actors' => 'Blocked accounts', + 'fediverse-blocked-domains' => 'Blocked domains', + 'users' => 'Users', + 'user-list' => 'All users', + 'user-create' => 'New user', + 'pages' => 'Pages', + 'page-list' => 'All pages', + 'page-create' => 'New Page', + 'settings' => 'Settings', + 'settings-general' => 'General', + 'settings-theme' => 'Theme', + 'admin-about' => 'About', + 'account' => [ + 'my-account' => 'My account', + 'change-password' => 'Change password', + 'logout' => 'Logout', + ], +]; diff --git a/modules/Admin/Language/ko/Notifications.php b/modules/Admin/Language/ko/Notifications.php new file mode 100644 index 00000000..2b139d51 --- /dev/null +++ b/modules/Admin/Language/ko/Notifications.php @@ -0,0 +1,19 @@ + 'Notifications', + 'reply' => '{actor_username} replied to your post', + 'favourite' => '{actor_username} favourited your post', + 'reblog' => '{actor_username} shared your post', + 'follow' => '{actor_username} started following you', + 'no_notifications' => 'No notifications', + 'mark_all_as_read' => 'Mark all as read', +]; diff --git a/modules/Admin/Language/ko/Page.php b/modules/Admin/Language/ko/Page.php new file mode 100644 index 00000000..b6f49de5 --- /dev/null +++ b/modules/Admin/Language/ko/Page.php @@ -0,0 +1,30 @@ + 'Back to home', + 'page' => 'Page', + 'all_pages' => 'All pages', + 'create' => 'New page', + 'go_to_page' => 'Go to page', + 'edit' => 'Edit page', + 'delete' => 'Delete page', + 'form' => [ + 'title' => 'Title', + 'permalink' => 'Permalink', + 'content' => 'Content', + 'submit_create' => 'Create page', + 'submit_edit' => 'Save', + ], + 'messages' => [ + 'createSuccess' => 'The page “{pageTitle}” was created successfully!', + 'editSuccess' => 'The page was successfully updated!', + ], +]; diff --git a/modules/Admin/Language/ko/Pager.php b/modules/Admin/Language/ko/Pager.php new file mode 100644 index 00000000..e25ee638 --- /dev/null +++ b/modules/Admin/Language/ko/Pager.php @@ -0,0 +1,21 @@ + 'Page navigation', + 'first' => 'First', + 'previous' => 'Previous', + 'next' => 'Next', + 'last' => 'Last', + 'older' => 'Older', + 'newer' => 'Newer', + 'invalidTemplate' => '{0} is not a valid Pager template.', + 'invalidPaginationGroup' => '{0} is not a valid Pagination group.', +]; diff --git a/modules/Admin/Language/ko/Person.php b/modules/Admin/Language/ko/Person.php new file mode 100644 index 00000000..a652be9f --- /dev/null +++ b/modules/Admin/Language/ko/Person.php @@ -0,0 +1,65 @@ + 'Persons', + 'all_persons' => 'All persons', + 'no_person' => 'Nobody found!', + 'create' => 'Create a person', + 'view' => 'View person', + 'edit' => 'Edit person', + 'delete' => 'Delete person', + 'messages' => [ + 'createSuccess' => 'Person has been successfully created!', + 'editSuccess' => 'Person has been successfully updated!', + 'deleteSuccess' => 'Person has been removed!', + ], + 'form' => [ + 'avatar' => 'Avatar', + 'avatar_size_hint' => + 'Avatar must be squared and at least 400px wide and tall.', + 'full_name' => 'Full name', + 'full_name_hint' => 'This is the full name or alias of the person.', + 'unique_name' => 'Unique name', + 'unique_name_hint' => 'Used for URLs', + 'information_url' => 'Information URL', + 'information_url_hint' => + 'Url to a relevant resource of information about the person, such as a homepage or third-party profile platform.', + 'submit_create' => 'Create person', + 'submit_edit' => 'Save person', + ], + 'podcast_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this podcast', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'episode_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this episode', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'credits' => 'Credits', +]; diff --git a/modules/Admin/Language/ko/Platforms.php b/modules/Admin/Language/ko/Platforms.php new file mode 100644 index 00000000..e161181c --- /dev/null +++ b/modules/Admin/Language/ko/Platforms.php @@ -0,0 +1,43 @@ + [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', + 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', + 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', + 'visible' => 'Display in podcast homepage?', + 'on_embed' => 'Display on embeddable player?', + 'remove' => 'Remove {platformName}', + 'submit' => 'Save', + 'messages' => [ + 'updateSuccess' => 'Platform links have been successfully updated!', + 'removeLinkSuccess' => 'The platform link has been removed.', + 'removeLinkError' => + 'The platform link could not be removed. Try again.', + ], + 'description' => [ + 'podcasting' => 'The podcast ID on this platform', + 'social' => 'The podcast account ID on this platform', + 'funding' => 'Call to action message', + ], +]; diff --git a/modules/Admin/Language/ko/Podcast.php b/modules/Admin/Language/ko/Podcast.php new file mode 100644 index 00000000..ff0daebc --- /dev/null +++ b/modules/Admin/Language/ko/Podcast.php @@ -0,0 +1,330 @@ + 'All podcasts', + 'no_podcast' => 'No podcast found!', + 'create' => 'Create podcast', + 'import' => 'Import podcast', + 'all_imports' => 'Podcast imports', + 'new_episode' => 'New Episode', + 'view' => 'View podcast', + 'edit' => 'Edit podcast', + 'publish' => 'Publish podcast', + 'publish_edit' => 'Edit publication', + 'delete' => 'Delete podcast', + 'see_episodes' => 'See episodes', + 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', + 'go_to_page' => 'Go to page', + 'latest_episodes' => 'Latest episodes', + 'see_all_episodes' => 'See all episodes', + 'draft' => 'Draft', + 'messages' => [ + 'createSuccess' => 'Podcast successfully created!', + 'editSuccess' => 'Podcast has been successfully updated!', + 'importSuccess' => 'Podcast has been successfully imported!', + 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', + 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, + cover {cover} + banner {banner} + other {media} + }.', + 'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.', + 'podcastFeedUpdateSuccess' => 'Successful update: {number_of_new_episodes, plural, + one {# episode was} + other {# episodes were} + } added to the podcast!', + 'podcastFeedUpToDate' => 'Podcast is already up to date.', + 'publishError' => 'This podcast is either already published or scheduled for publication.', + 'publishEditError' => 'This podcast is not scheduled for publication.', + 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', + 'scheduleDateError' => 'Schedule date must be set!', + ], + 'form' => [ + 'identity_section_title' => 'Podcast identity', + 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + + 'cover' => 'Podcast cover', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'banner' => 'Podcast banner', + 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', + 'banner_delete' => 'Delete podcast banner', + 'title' => 'Title', + 'handle' => 'Handle', + 'handle_hint' => + 'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.', + 'type' => [ + 'label' => 'Type', + 'episodic' => 'Episodic', + 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', + 'serial' => 'Serial', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + ], + 'description' => 'Description', + 'classification_section_title' => 'Classification', + 'classification_section_subtitle' => + 'These fields will impact your audience and competition.', + 'language' => 'Language', + 'category' => 'Category', + 'category_placeholder' => 'Select a category…', + 'other_categories' => 'Other categories', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does it contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'author_section_title' => 'Author', + 'author_section_subtitle' => 'Who is managing the podcast?', + 'owner_name' => 'Owner name', + 'owner_name_hint' => + 'For administrative use only. Visible in the public RSS feed.', + 'owner_email' => 'Owner email', + 'owner_email_hint' => + 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', + 'publisher' => 'Publisher', + 'publisher_hint' => + 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', + 'copyright' => 'Copyright', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this podcast about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real place or fictional', + 'monetization_section_title' => 'Monetization', + 'monetization_section_subtitle' => + 'Earn money thanks to your audience.', + 'premium' => 'Premium', + 'premium_by_default' => 'Episodes must be set as premium by default', + 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', + 'payment_pointer' => 'Payment Pointer for Web Monetization', + 'payment_pointer_hint' => + 'This is your where you will receive money thanks to Web Monetization', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the podcast', + 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', + 'new_feed_url' => 'New feed URL', + 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', + 'old_feed_url' => 'Old feed URL', + 'partnership' => 'Partnership', + 'partner_id' => 'ID', + 'partner_link_url' => 'Link URL', + 'partner_image_url' => 'Image URL', + 'partner_id_hint' => 'Your own partner ID', + 'partner_link_url_hint' => 'The generic partner link address', + 'partner_image_url_hint' => 'The generic partner image address', + 'block' => 'Podcast should be hidden from public catalogues', + 'block_hint' => + 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'complete' => 'Podcast will not be having new episodes', + 'lock' => 'Prevent podcast from being copied', + 'lock_hint' => + 'The purpose is to tell other podcast platforms whether they are allowed to import this feed. A value of yes means that any attempt to import this feed into a new platform should be rejected.', + 'submit_create' => 'Create podcast', + 'submit_edit' => 'Save podcast', + ], + 'category_options' => [ + 'uncategorized' => 'uncategorized', + 'arts' => 'Arts', + 'business' => 'Business', + 'comedy' => 'Comedy', + 'education' => 'Education', + 'fiction' => 'Fiction', + 'government' => 'Government', + 'health_and_fitness' => 'Health & Fitness', + 'history' => 'History', + 'kids_and_family' => 'Kids & Family', + 'leisure' => 'Leisure', + 'music' => 'Music', + 'news' => 'News', + 'religion_and_spirituality' => 'Religion & Spirituality', + 'science' => 'Science', + 'society_and_culture' => 'Society & Culture', + 'sports' => 'Sports', + 'technology' => 'Technology', + 'true_crime' => 'True Crime', + 'tv_and_film' => 'TV & Film', + 'books' => 'Books', + 'design' => 'Design', + 'fashion_and_beauty' => 'Fashion & Beauty', + 'food' => 'Food', + 'performing_arts' => 'Performing Arts', + 'visual_arts' => 'Visual Arts', + 'careers' => 'Careers', + 'entrepreneurship' => 'Entrepreneurship', + 'investing' => 'Investing', + 'management' => 'Management', + 'marketing' => 'Marketing', + 'non_profit' => 'Non-Profit', + 'comedy_interviews' => 'Comedy Interviews', + 'improv' => 'Improv', + 'stand_up' => 'Stand-Up', + 'courses' => 'Courses', + 'how_to' => 'How To', + 'language_learning' => 'Language Learning', + 'self_improvement' => 'Self-Improvement', + 'comedy_fiction' => 'Comedy Fiction', + 'drama' => 'Drama', + 'science_fiction' => 'Science Fiction', + 'alternative_health' => 'Alternative Health', + 'fitness' => 'Fitness', + 'medicine' => 'Medicine', + 'mental_health' => 'Mental Health', + 'nutrition' => 'Nutrition', + 'sexuality' => 'Sexuality', + 'education_for_kids' => 'Education for Kids', + 'parenting' => 'Parenting', + 'pets_and_animals' => 'Pets & Animals', + 'stories_for_kids' => 'Stories for Kids', + 'animation_and_manga' => 'Animation & Manga', + 'automotive' => 'Automotive', + 'aviation' => 'Aviation', + 'crafts' => 'Crafts', + 'games' => 'Games', + 'hobbies' => 'Hobbies', + 'home_and_garden' => 'Home & Garden', + 'video_games' => 'Video Games', + 'music_commentary' => 'Music Commentary', + 'music_history' => 'Music History', + 'music_interviews' => 'Music Interviews', + 'business_news' => 'Business News', + 'daily_news' => 'Daily News', + 'entertainment_news' => 'Entertainment News', + 'news_commentary' => 'News Commentary', + 'politics' => 'Politics', + 'sports_news' => 'Sports News', + 'tech_news' => 'Tech News', + 'buddhism' => 'Buddhism', + 'christianity' => 'Christianity', + 'hinduism' => 'Hinduism', + 'islam' => 'Islam', + 'judaism' => 'Judaism', + 'religion' => 'Religion', + 'spirituality' => 'Spirituality', + 'astronomy' => 'Astronomy', + 'chemistry' => 'Chemistry', + 'earth_sciences' => 'Earth Sciences', + 'life_sciences' => 'Life Sciences', + 'mathematics' => 'Mathematics', + 'natural_sciences' => 'Natural Sciences', + 'nature' => 'Nature', + 'physics' => 'Physics', + 'social_sciences' => 'Social Sciences', + 'documentary' => 'Documentary', + 'personal_journals' => 'Personal Journals', + 'philosophy' => 'Philosophy', + 'places_and_travel' => 'Places & Travel', + 'relationships' => 'Relationships', + 'baseball' => 'Baseball', + 'basketball' => 'Basketball', + 'cricket' => 'Cricket', + 'fantasy_sports' => 'Fantasy Sports', + 'football' => 'Football', + 'golf' => 'Golf', + 'hockey' => 'Hockey', + 'rugby' => 'Rugby', + 'running' => 'Running', + 'soccer' => 'Soccer', + 'swimming' => 'Swimming', + 'tennis' => 'Tennis', + 'volleyball' => 'Volleyball', + 'wilderness' => 'Wilderness', + 'wrestling' => 'Wrestling', + 'after_shows' => 'After Shows', + 'film_history' => 'Film History', + 'film_interviews' => 'Film Interviews', + 'film_reviews' => 'Film Reviews', + 'tv_reviews' => 'TV Reviews', + ], + 'publish_form' => [ + 'back_to_podcast_dashboard' => 'Back to podcast dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'submit' => 'Publish', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', + 'message_warning_submit' => 'Publish anyway', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'not_published' => 'This podcast is not yet published.', + 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", + 'understand' => 'I understand, I want the podcast to be permanently deleted', + 'submit' => 'Delete', + ], + 'by' => 'By {publisher}', + 'season' => 'Season {seasonNumber}', + 'list_of_episodes_year' => '{year} episodes ({episodeCount})', + 'list_of_episodes_season' => + 'Season {seasonNumber} episodes ({episodeCount})', + 'no_episode' => 'No episode found!', + 'follow' => 'Follow', + 'followers' => '{numberOfFollowers, plural, + one {# follower} + other {# followers} + }', + 'posts' => '{numberOfPosts, plural, + one {# post} + other {# posts} + }', + 'activity' => 'Activity', + 'episodes' => 'Episodes', + 'sponsor' => 'Sponsor', + 'funding_links' => 'Funding links for {podcastTitle}', + 'find_on' => 'Find {podcastTitle} on', + 'listen_on' => 'Listen on', +]; diff --git a/modules/Admin/Language/ko/PodcastNavigation.php b/modules/Admin/Language/ko/PodcastNavigation.php new file mode 100644 index 00000000..bb777707 --- /dev/null +++ b/modules/Admin/Language/ko/PodcastNavigation.php @@ -0,0 +1,42 @@ + 'Go to podcast page', + 'rss_feed' => 'RSS feed', + 'dashboard' => 'Podcast dashboard', + 'podcast-view' => 'Home', + 'podcast-edit' => 'Edit podcast', + 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', + 'episodes' => 'Episodes', + 'episode-list' => 'All episodes', + 'episode-create' => 'New episode', + 'analytics' => 'Analytics', + 'podcast-analytics' => 'Audience overview', + 'podcast-analytics-webpages' => 'Web pages visits', + 'podcast-analytics-locations' => 'Locations', + 'podcast-analytics-unique-listeners' => 'Unique listeners', + 'podcast-analytics-players' => 'Players', + 'podcast-analytics-listening-time' => 'Listening time', + 'podcast-analytics-time-periods' => 'Time periods', + 'monetization' => 'Monetization', + 'subscription-list' => 'All subscriptions', + 'subscription-create' => 'Add subscription', + 'contributors' => 'Contributors', + 'contributor-list' => 'All contributors', + 'contributor-add' => 'Add contributor', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', + 'platforms-social' => 'Social networks', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', +]; diff --git a/modules/Admin/Language/ko/Settings.php b/modules/Admin/Language/ko/Settings.php new file mode 100644 index 00000000..4a70dcba --- /dev/null +++ b/modules/Admin/Language/ko/Settings.php @@ -0,0 +1,58 @@ + 'General settings', + 'instance' => [ + 'title' => 'Instance', + 'site_icon' => 'Site icon', + 'site_icon_delete' => 'Delete site icon', + 'site_icon_hint' => 'Site icons are what you see on your browser tabs, bookmarks bar, and when you add a website as a shortcut on mobile devices.', + 'site_icon_helper' => 'Icon must be squared and at least 512px wide and tall.', + 'site_name' => 'Site name', + 'site_description' => 'Site description', + 'submit' => 'Save', + 'editSuccess' => 'Instance has been updated successfully!', + 'deleteIconSuccess' => 'Site icon has been remove successfully!', + ], + 'images' => [ + 'title' => 'Images', + 'subtitle' => 'Here you can regenerate all images based on the originals that were uploaded. To be used if you find that some images are missing. This task may take a while.', + 'regenerate' => 'Regenerate images', + 'regenerationSuccess' => 'All images have been regenerated successfully!', + ], + 'housekeeping' => [ + 'title' => 'Housekeeping', + 'subtitle' => 'Runs various housekeeping tasks. Use this feature if you ever encounter issues with media files or data integrity. These tasks may take a while.', + 'reset_counts' => 'Reset counts', + 'reset_counts_helper' => 'This option will recalculate and reset all data counts (number of followers, posts, comments, …).', + 'rewrite_media' => 'Rewrite media metadata', + 'rewrite_media_helper' => 'This option will delete all superfluous media files and recreate them (images, audio files, transcripts, chapters, …)', + 'rename_episodes_files' => 'Rename episode audio files', + 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', + 'clear_cache' => 'Clear all cache', + 'clear_cache_helper' => 'This option will flush redis cache or writable/cache files.', + 'run' => 'Run housekeeping', + 'runSuccess' => 'Housekeeping has been run successfully!', + ], + 'theme' => [ + 'title' => 'Theme', + 'accent_section_title' => 'Accent color', + 'accent_section_subtitle' => 'Choose the color to determine the look and feel of all public pages.', + 'pine' => 'Pine', + 'crimson' => 'Crimson', + 'amber' => 'Amber', + 'lake' => 'Lake', + 'jacaranda' => 'Jacaranda', + 'onyx' => 'Onyx', + 'submit' => 'Save', + 'setInstanceThemeSuccess' => 'Theme has been updated successfully!', + ], +]; diff --git a/modules/Admin/Language/ko/Soundbite.php b/modules/Admin/Language/ko/Soundbite.php new file mode 100644 index 00000000..a3f828fe --- /dev/null +++ b/modules/Admin/Language/ko/Soundbite.php @@ -0,0 +1,31 @@ + [ + 'title' => 'Soundbites', + 'soundbite' => 'Soundbite', + ], + 'messages' => [ + 'createSuccess' => 'Soundbite has been successfully created!', + 'deleteSuccess' => 'Soundbite has been successfully removed!', + ], + 'form' => [ + 'title' => 'New soundbite', + 'soundbite_title' => 'Soundbite title', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'submit' => 'Create soundbite', + ], + 'play' => 'Play soundbite', + 'stop' => 'Stop soundbite', + 'create' => 'New soundbite', + 'delete' => 'Delete soundbite', +]; diff --git a/modules/Admin/Language/ko/Validation.php b/modules/Admin/Language/ko/Validation.php new file mode 100644 index 00000000..f76c3163 --- /dev/null +++ b/modules/Admin/Language/ko/Validation.php @@ -0,0 +1,17 @@ + + '{field} is either not an image, or it is not wide or tall enough.', + 'is_image_ratio' => + '{field} is either not an image or not of the right ratio.', + 'is_json' => '{field} contains invalid JSON.', +]; diff --git a/modules/Admin/Language/ko/VideoClip.php b/modules/Admin/Language/ko/VideoClip.php new file mode 100644 index 00000000..638de697 --- /dev/null +++ b/modules/Admin/Language/ko/VideoClip.php @@ -0,0 +1,72 @@ + [ + 'title' => 'Video clips', + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Clip is waiting to be processed.', + 'pending' => 'pending', + 'pending_hint' => 'Clip will be generated shortly.', + 'running' => 'running', + 'running_hint' => 'Clip is being generated.', + 'failed' => 'failed', + 'failed_hint' => 'Clip could not be generated: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Clip was generated successfully!', + ], + 'clip' => 'Clip', + 'duration' => 'Job duration', + ], + 'title' => 'Video clip: {videoClipLabel}', + 'download_clip' => 'Download clip', + 'create' => 'New video clip', + 'go_to_page' => 'Go to clip page', + 'retry' => 'Retry clip generation', + 'delete' => 'Delete clip', + 'logs' => 'Job logs', + 'messages' => [ + 'alreadyExistingError' => 'The video clip you are trying to create already exists!', + 'addToQueueSuccess' => 'Video clip has been added to queue, awaiting to be created!', + 'deleteSuccess' => 'Video clip has been successfully removed!', + ], + 'format' => [ + 'landscape' => 'Landscape', + 'portrait' => 'Portrait', + 'squared' => 'Squared', + ], + 'form' => [ + 'title' => 'New video clip', + 'params_section_title' => 'Video clip parameters', + 'clip_title' => 'Clip title', + 'format' => [ + 'label' => 'Choose a format', + 'landscape_hint' => 'With a 16:9 ratio, landscape videos are great for PeerTube, Youtube and Vimeo.', + 'portrait_hint' => 'With a 9:16 ratio, portrait videos are great for TikTok, Youtube shorts and Instagram stories.', + 'squared_hint' => 'With a 1:1 ratio, squared videos are great for Mastodon, Facebook, Twitter and LinkedIn.', + ], + 'theme' => 'Select a theme', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'trim_start' => 'Trim start', + 'trim_end' => 'Trim end', + 'submit' => 'Create video clip', + ], + 'requirements' => [ + 'title' => 'Missing requirements', + 'missing' => 'You have missing requirements. Make sure to add all the required items to be allowed creating a video for this episode!', + 'ffmpeg' => 'FFmpeg', + 'gd' => 'Graphics Draw (GD)', + 'freetype' => 'Freetype library for GD', + 'transcript' => 'Transcript file (.srt)', + ], +]; diff --git a/modules/Admin/Language/nl/AboutCastopod.php b/modules/Admin/Language/nl/AboutCastopod.php new file mode 100644 index 00000000..efefa11f --- /dev/null +++ b/modules/Admin/Language/nl/AboutCastopod.php @@ -0,0 +1,22 @@ + 'Over Castopod', + 'host_name' => 'Servernaam', + 'version' => 'Castopod versie', + 'php_version' => 'PHP versie', + 'os' => 'Besturingssystem', + 'languages' => 'Talen', + 'update_database' => 'Database bijwerken', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up-to-date!', + ], +]; diff --git a/modules/Admin/Language/nl/Breadcrumb.php b/modules/Admin/Language/nl/Breadcrumb.php index a5c08f43..2d120967 100644 --- a/modules/Admin/Language/nl/Breadcrumb.php +++ b/modules/Admin/Language/nl/Breadcrumb.php @@ -14,39 +14,44 @@ return [ ->gateway => 'Hoofdpagina', 'podcasts' => 'podcasts', 'episodes' => 'afleveringen', - 'subscriptions' => 'subscriptions', + 'subscriptions' => 'abonnementen', 'contributors' => 'bijdragers', 'pages' => 'paginas', 'settings' => 'instellingen', 'theme' => 'thema', + 'about' => 'over', 'add' => 'toevoegen', 'new' => 'nieuw', 'edit' => 'bewerken', 'persons' => 'personen', 'publish' => 'publiceren', 'publish-edit' => 'publicatie aanpassen', - 'publish-date-edit' => 'edit publication date', + 'publish-date-edit' => 'publicatiedatum bewerken', 'unpublish' => 'publicatie ongedaan maken', 'delete' => 'verwijder', + 'remove' => 'verwijder', 'fediverse' => 'fediverse', - 'block-lists' => 'blokkeerlijst', + 'blocked-actors' => 'geblokkeerde actoren', + 'blocked-domains' => 'geblokkeerde domeinen', 'users' => 'gebruikers', 'my-account' => 'mijn account', 'change-password' => 'wachtwoord wijzigen', - 'import' => 'feed importeren', - 'platforms' => 'platforms', + 'imports' => 'imports', + 'sync-feeds' => 'feeds synchroniseren', + 'platforms' => 'platformen', 'social' => 'sociale netwerken', 'funding' => 'financiering', + 'monetization-other' => 'andere inkomsten', 'analytics' => 'statistieken', 'locations' => 'locaties', - 'webpages' => 'web pagina\'s', + 'webpages' => 'webpagina\'s', 'unique-listeners' => 'unieke luisteraars', 'players' => 'spelers', 'listening-time' => 'afspeeltijd', - 'time-periods' => 'tijds blok', - 'soundbites' => 'geluidsbeet', - 'video-clips' => 'video clips', - 'embed' => 'integreerbare speler', + 'time-periods' => 'tijdspanne', + 'soundbites' => 'geluidsfragment', + 'video-clips' => 'videoclips', + 'embed' => 'embedbare speler', 'notifications' => 'meldingen', - 'suspend' => 'suspend', + 'suspend' => 'opschorten', ]; diff --git a/modules/Admin/Language/nl/Charts.php b/modules/Admin/Language/nl/Charts.php index 6f2b1ea5..213e94ab 100644 --- a/modules/Admin/Language/nl/Charts.php +++ b/modules/Admin/Language/nl/Charts.php @@ -37,4 +37,5 @@ return [ 'podcast_by_bandwidth' => 'Dagelijks gebruikte bandbreedte (in MB)', 'total_storage_by_month' => 'Maandelijkse opslagruimte (in MB)', 'total_bandwidth_by_month' => 'Maandelijkse gebruikte bandbreedte (in MB)', + 'total_bandwidth_by_month_limit' => 'Gelimiteerd tot {totalBandwidth} per maand', ]; diff --git a/modules/Admin/Language/nl/Common.php b/modules/Admin/Language/nl/Common.php index a6194f73..65bbbdfe 100644 --- a/modules/Admin/Language/nl/Common.php +++ b/modules/Admin/Language/nl/Common.php @@ -40,6 +40,7 @@ return [ ], 'upload_file' => 'Bestand uploaden', 'remote_url' => 'Externe URL', + 'save' => 'Opslaan', ], 'play_episode_button' => [ 'play' => 'Afspelen', diff --git a/modules/Admin/Language/nl/Countries.php b/modules/Admin/Language/nl/Countries.php index 7652b137..82cb1990 100644 --- a/modules/Admin/Language/nl/Countries.php +++ b/modules/Admin/Language/nl/Countries.php @@ -94,171 +94,171 @@ return [ 'GG' => 'Guernsey', 'GH' => 'Ghana', 'GI' => 'Gibraltar', - 'GL' => 'Greenland', + 'GL' => 'Groenland', 'GM' => 'Gambia', - 'GN' => 'Guinea', + 'GN' => 'Guinee', 'GP' => 'Guadeloupe', - 'GQ' => 'Equatorial Guinea', - 'GR' => 'Greece', - 'GS' => 'South Georgia and the South Sandwich Islands', + 'GQ' => 'Equatoriaal-Guinea', + 'GR' => 'Griekenland', + 'GS' => 'Zuid-Georgia en de Zuidelijke Sandwicheilanden', 'GT' => 'Guatemala', 'GU' => 'Guam', - 'GW' => 'Guinea-Bissau', + 'GW' => 'Guinee-Bissau', 'GY' => 'Guyana', 'HK' => 'Hong Kong', - 'HM' => 'Heard Island and McDonald Islands', + 'HM' => 'Heard- en MacDonaldeilanden', 'HN' => 'Honduras', - 'HR' => 'Croatia', - 'HT' => 'Haiti', - 'HU' => 'Hungary', - 'ID' => 'Indonesia', - 'IE' => 'Ireland', - 'IL' => 'Israel', + 'HR' => 'Kroatië', + 'HT' => 'Haïti', + 'HU' => 'Hongarije', + 'ID' => 'Indonesië', + 'IE' => 'Ierland', + 'IL' => 'Israël', 'IM' => 'Isle of Man', 'IN' => 'India', - 'IO' => 'British Indian Ocean Territory', - 'IQ' => 'Iraq', - 'IR' => 'Iran, Islamic Republic of', - 'IS' => 'Iceland', - 'IT' => 'Italy', + 'IO' => 'Britse Gebieden in de Indische Oceaan', + 'IQ' => 'Irak', + 'IR' => 'Iran, Islamitische Republiek', + 'IS' => 'Ijsland', + 'IT' => 'Italië', 'JE' => 'Jersey', 'JM' => 'Jamaica', - 'JO' => 'Jordan', + 'JO' => 'Jordanië', 'JP' => 'Japan', - 'KE' => 'Kenya', - 'KG' => 'Kyrgyzstan', - 'KH' => 'Cambodia', + 'KE' => 'Kenia', + 'KG' => 'Kirgizië', + 'KH' => 'Cambodja', 'KI' => 'Kiribati', - 'KM' => 'Comoros', - 'KN' => 'Saint Kitts and Nevis', - 'KP' => "Korea, Democratic People's Republic of", - 'KR' => 'Korea, Republic of', - 'KW' => 'Kuwait', - 'KY' => 'Cayman Islands', - 'KZ' => 'Kazakhstan', - 'LA' => "Lao People's Democratic Republic", + 'KM' => 'Comoren', + 'KN' => 'Saint Kitts en Nevis', + 'KP' => "Korea, Democratische Volksrepubliek", + 'KR' => 'Zuid-Korea', + 'KW' => 'Koeweit', + 'KY' => 'Kaaimaneilanden', + 'KZ' => 'Kazachstan', + 'LA' => "Lao Democratische Volksrepubliek", 'LB' => 'Lebanon', - 'LC' => 'Saint Lucia', + 'LC' => 'Sint Lucia', 'LI' => 'Liechtenstein', 'LK' => 'Sri Lanka', 'LR' => 'Liberia', 'LS' => 'Lesotho', - 'LT' => 'Lithuania', - 'LU' => 'Luxembourg', - 'LV' => 'Latvia', - 'LY' => 'Libya', - 'MA' => 'Morocco', + 'LT' => 'Litauen', + 'LU' => 'Luxemburg', + 'LV' => 'Letland', + 'LY' => 'Libië', + 'MA' => 'Marokko', 'MC' => 'Monaco', - 'MD' => 'Moldova, Republic of', + 'MD' => 'Moldavië, Republiek', 'ME' => 'Montenegro', - 'MF' => 'Saint Martin (French part)', - 'MG' => 'Madagascar', - 'MH' => 'Marshall Islands', - 'MK' => 'Macedonia, the Former Yugoslav Republic of', + 'MF' => 'Sint Maarten (Frans deel)', + 'MG' => 'Madagaskar', + 'MH' => 'Marshalleilanden', + 'MK' => 'Macedonië', 'ML' => 'Mali', 'MM' => 'Myanmar', - 'MN' => 'Mongolia', - 'MO' => 'Macao', - 'MP' => 'Northern Mariana Islands', + 'MN' => 'Mongolië', + 'MO' => 'Macau', + 'MP' => 'Noordelijke Mariana eilanden', 'MQ' => 'Martinique', - 'MR' => 'Mauritania', + 'MR' => 'Mauritanië', 'MS' => 'Montserrat', 'MT' => 'Malta', 'MU' => 'Mauritius', - 'MV' => 'Maldives', + 'MV' => 'Maldiven', 'MW' => 'Malawi', 'MX' => 'Mexico', - 'MY' => 'Malaysia', + 'MY' => 'Maleisië', 'MZ' => 'Mozambique', - 'N/A' => 'Not Applicable (local IP…)', - 'NA' => 'Namibia', - 'NC' => 'New Caledonia', + 'N/A' => 'Niet van toepassing (lokale IP…)', + 'NA' => 'Namibië', + 'NC' => 'Nieuw-Caledonië', 'NE' => 'Niger', - 'NF' => 'Norfolk Island', + 'NF' => 'Norfolk Eiland', 'NG' => 'Nigeria', 'NI' => 'Nicaragua', - 'NL' => 'Netherlands', - 'NO' => 'Norway', + 'NL' => 'Nederland', + 'NO' => 'Noorwegen', 'NP' => 'Nepal', 'NR' => 'Nauru', 'NU' => 'Niue', - 'NZ' => 'New Zealand', + 'NZ' => 'Nieuw-Zeeland', 'OM' => 'Oman', 'PA' => 'Panama', 'PE' => 'Peru', - 'PF' => 'French Polynesia', - 'PG' => 'Papua New Guinea', - 'PH' => 'Philippines', + 'PF' => 'Frans-Polynesië', + 'PG' => 'Papua Nieuw-Guinea', + 'PH' => 'Filipijnen', 'PK' => 'Pakistan', - 'PL' => 'Poland', - 'PM' => 'Saint Pierre and Miquelon', + 'PL' => 'Polen', + 'PM' => 'Saint-Pierre en Miquelon', 'PN' => 'Pitcairn', 'PR' => 'Puerto Rico', - 'PS' => 'Palestine, State of', + 'PS' => 'Palestina', 'PT' => 'Portugal', 'PW' => 'Palau', 'PY' => 'Paraguay', 'QA' => 'Qatar', 'RE' => 'Réunion', - 'RO' => 'Romania', - 'RS' => 'Serbia', - 'RU' => 'Russian Federation', + 'RO' => 'Roemenië', + 'RS' => 'Servië', + 'RU' => 'Russische Federatie', 'RW' => 'Rwanda', - 'SA' => 'Saudi Arabia', - 'SB' => 'Solomon Islands', - 'SC' => 'Seychelles', - 'SD' => 'Sudan', - 'SE' => 'Sweden', + 'SA' => 'Saudi-Arabië', + 'SB' => 'Solomon-eilanden', + 'SC' => 'Seychellen', + 'SD' => 'Soedan', + 'SE' => 'Zweden', 'SG' => 'Singapore', - 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', - 'SI' => 'Slovenia', - 'SJ' => 'Svalbard and Jan Mayen', - 'SK' => 'Slovakia', - 'SL' => 'Sierra Leone', + 'SH' => 'Sint-Helena, Ascension en Tristan da Cunha', + 'SI' => 'Slovenië', + 'SJ' => 'Spitsbergen en Jan Mayen', + 'SK' => 'Slowakije', + 'SL' => 'Siërra Leone', 'SM' => 'San Marino', 'SN' => 'Senegal', - 'SO' => 'Somalia', + 'SO' => 'Somalië', 'SR' => 'Suriname', - 'SS' => 'South Sudan', - 'ST' => 'Sao Tome and Principe', + 'SS' => 'Zuid-Soedan', + 'ST' => 'Sao Tomé en Principe', 'SV' => 'El Salvador', - 'SX' => 'Sint Maarten (Dutch part)', - 'SY' => 'Syrian Arab Republic', + 'SX' => 'Sint Maarten (Nederlands deel)', + 'SY' => 'Syrië, Arabische Republiek', 'SZ' => 'Swaziland', - 'TC' => 'Turks and Caicos Islands', - 'TD' => 'Chad', - 'TF' => 'French Southern Territories', + 'TC' => 'Turks- en Caicos-eilanden', + 'TD' => 'Tsjaad', + 'TF' => 'Franse Gebieden in de zuidelijke Indische Oceaan', 'TG' => 'Togo', 'TH' => 'Thailand', - 'TJ' => 'Tajikistan', + 'TJ' => 'Tadzjikistan', 'TK' => 'Tokelau', - 'TL' => 'Timor-Leste', + 'TL' => 'Oost-Timor', 'TM' => 'Turkmenistan', - 'TN' => 'Tunisia', + 'TN' => 'Tunesië', 'TO' => 'Tonga', - 'TR' => 'Turkey', - 'TT' => 'Trinidad and Tobago', + 'TR' => 'Turkije', + 'TT' => 'Trinidad en Tobago', 'TV' => 'Tuvalu', - 'TW' => 'Taiwan, Province of China', - 'TZ' => 'Tanzania, United Republic of', - 'UA' => 'Ukraine', - 'UG' => 'Uganda', - 'UM' => 'United States Minor Outlying Islands', - 'US' => 'United States', + 'TW' => 'Taiwan', + 'TZ' => 'Tanzania', + 'UA' => 'Oekraïne', + 'UG' => 'Oeganda', + 'UM' => 'Kleine afgelegen eilanden van de Verenigde Staten', + 'US' => 'Verenigde Staten', 'UY' => 'Uruguay', - 'UZ' => 'Uzbekistan', - 'VA' => 'Holy See (Vatican City State)', - 'VC' => 'Saint Vincent and the Grenadines', - 'VE' => 'Venezuela, Bolivarian Republic of', - 'VG' => 'Virgin Islands, British', - 'VI' => 'Virgin Islands, U.S.', - 'VN' => 'Viet Nam', + 'UZ' => 'Oezbekistan', + 'VA' => 'Holy See (Vaticaanstad)', + 'VC' => 'Sint Vincent en de Grenadines', + 'VE' => 'Venezuela, Bolivariaanse Republiek', + 'VG' => 'Britse Maagdeneilanden', + 'VI' => 'Maagdeneilanden, Amerikaanse', + 'VN' => 'Vietnam', 'VU' => 'Vanuatu', - 'WF' => 'Wallis and Futuna', + 'WF' => 'Wallis en Futuna', 'WS' => 'Samoa', - 'YE' => 'Yemen', + 'YE' => 'Jemen', 'YT' => 'Mayotte', - 'ZA' => 'South Africa', + 'ZA' => 'Zuid-Afrika', 'ZM' => 'Zambia', 'ZW' => 'Zimbabwe', ]; diff --git a/modules/Admin/Language/nl/Dashboard.php b/modules/Admin/Language/nl/Dashboard.php index 881073fd..15c43acb 100644 --- a/modules/Admin/Language/nl/Dashboard.php +++ b/modules/Admin/Language/nl/Dashboard.php @@ -9,20 +9,20 @@ declare(strict_types=1); */ return [ - 'home' => 'Admin dashboard', - 'welcome_message' => 'Welcome to the admin area!', + 'home' => 'Beheerdersdashboard', + 'welcome_message' => 'Welkom bij de beheeromgeving!', 'podcasts' => [ 'title' => 'Podcasts', - 'not_found' => 'No published podcast', - 'last_published' => 'Last published on {lastPublicationDate}', + 'not_found' => 'Geen gepubliceerde podcast', + 'last_published' => 'Laatst gepubliceerd op {lastPublicationDate}', ], 'episodes' => [ - 'title' => 'Episodes', - 'not_found' => 'No published episode', - 'last_published' => 'Last published on {lastPublicationDate}', + 'title' => 'Afleveringen', + 'not_found' => 'Geen gepubliceerde aflevering', + 'last_published' => 'Laatst gepubliceerd op {lastPublicationDate}', ], 'storage' => [ - 'title' => 'Storage', - 'subtitle' => '{totalUploaded} out of {totalStorage}', + 'title' => 'Opslagruimte', + 'subtitle' => '{totalUploaded} van {totalStorage}', ], ]; diff --git a/modules/Admin/Language/nl/Episode.php b/modules/Admin/Language/nl/Episode.php index 60845744..70a11518 100644 --- a/modules/Admin/Language/nl/Episode.php +++ b/modules/Admin/Language/nl/Episode.php @@ -22,39 +22,41 @@ return [ 'all_podcast_episodes' => 'Alle podcast afleveringen', 'back_to_podcast' => 'Terug naar podcast', 'edit' => 'Bewerken', + 'preview' => 'Voorbeeld', 'publish' => 'Publiceren', 'publish_edit' => 'Publicatie bewerken', - 'publish_date_edit' => 'Edit publication date', + 'publish_date_edit' => 'Publicatiedatum bewerken', 'unpublish' => 'Publicatie ongedaan maken', 'publish_error' => 'Aflevering is reeds gepubliceerd.', 'publish_edit_error' => 'Aflevering is reeds gepubliceerd.', 'publish_cancel_error' => 'Aflevering is reeds gepubliceerd.', - 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', - 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', - 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'publish_date_edit_error' => 'Aflevering is nog niet gepubliceerd, u kunt de publicatiedatum niet bewerken.', + 'publish_date_edit_future_error' => 'Aflevering publicatiedatum kan alleen worden ingesteld op een datum in het verleden! Als u het opnieuw wilt inplannen, verwijder dan de publicatie eerst.', + 'publish_date_edit_success' => 'De publicatiedatum van aflevering is met succes bijgewerkt!', 'unpublish_error' => 'Aflevering is niet gepubliceerd.', 'delete' => 'Verwijder', 'go_to_page' => 'Ga naar pagina', 'create' => 'Aflevering toevoegen', 'publication_status' => [ 'published' => 'Gepubliceerd', - 'with_podcast' => 'Published', + 'with_podcast' => 'Gepubliceerd', 'scheduled' => 'Gepland', 'not_published' => 'Niet gepubliceerd', ], - 'with_podcast_hint' => 'To be published at the same time as the podcast', + 'with_podcast_hint' => 'Nog te publiceren op hetzelfde moment als de podcast', 'list' => [ 'search' => [ - 'placeholder' => 'Search for an episode', - 'clear' => 'Clear search', - 'submit' => 'Search', + 'placeholder' => 'Zoek naar een aflevering', + 'clear' => 'Wis zoekopdracht', + 'submit' => 'Zoeken', ], 'number_of_episodes' => '{numberOfEpisodes, plural, - one {# episode} - other {# episodes} + one {# aflevering} + other {# afleveringen} }', 'episode' => 'Aflevering', 'visibility' => 'Zichtbaarheid', + 'downloads' => 'Downloads', 'comments' => 'Reacties', 'actions' => 'Acties', ], @@ -62,31 +64,31 @@ return [ 'createSuccess' => 'Aflevering is succesvol aangemaakt!', 'editSuccess' => 'Aflevering is succesvol bijgewerkt!', 'publishSuccess' => '{publication_status, select, - published {Episode successfully published!} - scheduled {Episode publication successfully scheduled!} - with_podcast {This episode will be published at the same time as the podcast.} - other {This episode is not published.} + published {Deze aflevering is nog niet gepubliceerd!} + scheduled {Deze aflevering is successvol gepland!} + with_podcast {Deze aflevering zal op hetzelfde moment als de podcast worden gepubliceerd.} + other {Deze aflevering is nog niet gepubliceerd.} }', 'publishCancelSuccess' => 'Aflevering publicatie is geannuleerd!', - 'unpublishBeforeDeleteTip' => 'You must unpublish the episode before deleting it.', - 'scheduleDateError' => 'Schedule date must be set!', - 'deletePublishedEpisodeError' => 'Please unpublish the episode before deleting it.', - 'deleteSuccess' => 'Episode successfully deleted!', - 'deleteError' => 'Failed to delete episode {type, select, + 'unpublishBeforeDeleteTip' => 'Je moet de publicatie van de aflevering ongedaan maken voordat je deze verwijdert.', + 'scheduleDateError' => 'Geplande datum moet worden ingesteld!', + 'deletePublishedEpisodeError' => 'Je moet de publicatie van de aflevering ongedaan maken voordat je deze verwijdert.', + 'deleteSuccess' => 'Aflevering succesvol verwijderd!', + 'deleteError' => 'Kan de aflevering niet verwijderen {type, select, transcript {transcript} - chapters {chapters} + chapters {hoofdstukken} image {cover} audio {audio} other {media} }.', - 'deleteFileError' => 'Failed to delete {type, select, + 'deleteFileError' => 'Mislukt om te verwijderen {type, select, transcript {transcript} - chapters {chapters} + chapters {hoofdstukken} image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', - 'sameSlugError' => 'An episode with the chosen slug already exists.', + } bestand {file_key}. Je kunt het handmatig verwijderen van je schijf.', + 'sameSlugError' => 'Er bestaat al een aflevering met de gekozen slug.', ], 'form' => [ 'file_size_error' => @@ -97,7 +99,7 @@ return [ 'cover' => 'Aflevering omslag', 'cover_hint' => 'Als je geen omslag instelt, zal de podcast omslag worden gebruikt.', - 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'cover_size_hint' => 'Omslag moet minstens 1400px breed en hoog zijn.', 'title' => 'Titel', 'title_hint' => 'Moet een duidelijke en beknopte afleveringsnaam bevatten. Geef hier geen aflevering of seizoen nummers op.', @@ -109,105 +111,115 @@ return [ 'full' => 'Vol', 'full_hint' => 'Volledige inhoud (aflevering)', 'trailer' => 'Trailer', - 'trailer_hint' => 'Short, promotional piece of content that represents a preview of the current show', + 'trailer_hint' => 'Korte, promotionele inhoud die een voorbeeld van de huidige show vertegenwoordigt', 'bonus' => 'Bonus', - 'bonus_hint' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show', + 'bonus_hint' => 'Extra inhoud voor de show (bijvoorbeeld achter de scène-info of interviews met de deelnemers) of cross-promotionele inhoud voor een andere show', ], 'premium_title' => 'Premium', - 'premium' => 'Episode must be accessible to premium subscribers only', + 'premium' => 'Aflevering mag alleen toegankelijk zijn voor premium abonnees', 'parental_advisory' => [ - 'label' => 'Parental advisory', - 'hint' => 'Does the episode contain explicit content?', - 'undefined' => 'undefined', - 'clean' => 'Clean', - 'explicit' => 'Explicit', + 'label' => 'Ouderlijk advies', + 'hint' => 'Bevat de aflevering de expliciete inhoud?', + 'undefined' => 'niet gedefineerd', + 'clean' => 'Fatsoenlijk', + 'explicit' => 'Expliciet', ], - 'show_notes_section_title' => 'Show notes', + 'show_notes_section_title' => 'Toon notities', 'show_notes_section_subtitle' => - 'Up to 4000 characters, be clear and concise. Show notes help potential listeners in finding the episode.', - 'description' => 'Description', - 'description_footer' => 'Description footer', + 'Maximaal 4000 tekens, wees duidelijk en beknopt. Notities helpen potentiële luisteraars om de aflevering te vinden.', + 'description' => 'Omschrijving', + 'description_footer' => 'Omschrijving voettekst', 'description_footer_hint' => - 'This text is added at the end of each episode description, it is a good place to input your social links for example.', - 'additional_files_section_title' => 'Additional files', + 'Deze tekst wordt aan het einde van elke aflevering beschrijving toegevoegd, het is een goede plek om bijvoorbeeld je sociale links te plaatsen.', + 'additional_files_section_title' => 'Extra bestanden', 'additional_files_section_subtitle' => - 'These files may be used by other platforms to provide better experience to your audience. See the {podcastNamespaceLink} for more information.', - 'location_section_title' => 'Location', - 'location_section_subtitle' => 'What place is this episode about?', - 'location_name' => 'Location name or address', - 'location_name_hint' => 'This can be a real or fictional location', - 'transcript' => 'Transcript (subtitles / closed captions)', - 'transcript_hint' => 'Only .srt are allowed.', - 'transcript_download' => 'Download transcript', - 'transcript_file' => 'Transcript file (.srt)', - 'transcript_remote_url' => 'Remote url for transcript', - 'transcript_file_delete' => 'Delete transcript file', - 'chapters' => 'Chapters', - 'chapters_hint' => 'File must be in JSON Chapters format.', - 'chapters_download' => 'Download chapters', - 'chapters_file' => 'Chapters file', - 'chapters_remote_url' => 'Remote url for chapters file', - 'chapters_file_delete' => 'Delete chapters file', - 'advanced_section_title' => 'Advanced Parameters', + 'Deze bestanden kunnen door andere platforms worden gebruikt om uw publiek een betere ervaring te bieden. Zie de {podcastNamespaceLink} voor meer informatie.', + 'location_section_title' => 'Locatie', + 'location_section_subtitle' => 'Over welke plaats gaat deze aflevering?', + 'location_name' => 'Locatienaam of adres', + 'location_name_hint' => 'Dit kan een echte of fictieve locatie zijn', + 'transcript' => 'Transcript (ondertiteling / gesloten ondertitels)', + 'transcript_hint' => 'Alleen .srt of .vtt zijn toegestaan.', + 'transcript_download' => 'Transcriptie downloaden', + 'transcript_file' => 'Transcript-bestand (.srt of .vtt)', + 'transcript_remote_url' => 'Externe URL voor transcript', + 'transcript_file_delete' => 'Verwijder transcript-bestand', + 'chapters' => 'Hoofdstukken', + 'chapters_hint' => 'Bestand moet in JSON Hoofdstuk indeling zijn.', + 'chapters_download' => 'Hoofdstukken downloaden', + 'chapters_file' => 'Hoofdstukken bestand', + 'chapters_remote_url' => 'Externe URL voor hoofdstukken bestand', + 'chapters_file_delete' => 'Verwijder hoofdstukken bestand', + 'advanced_section_title' => 'Geavanceerde parameters', 'advanced_section_subtitle' => - 'If you need RSS tags that Castopod does not handle, set them here.', - 'custom_rss' => 'Custom RSS tags for the episode', + 'Als je RSS tags nodig hebt die Castopod niet afhandelt, stel ze hier in.', + 'custom_rss' => 'Aangepaste RSS labels voor de aflevering', 'custom_rss_hint' => 'This will be injected within the ❬item❭ tag.', 'block' => 'Episode should be hidden from public catalogues', 'block_hint' => 'The episode show or hide status: toggling this on prevents the episode from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', - 'submit_create' => 'Create episode', - 'submit_edit' => 'Save episode', + 'submit_create' => 'Aflevering aanmaken', + 'submit_edit' => 'Aflevering opslaan', ], 'publish_form' => [ 'back_to_episode_dashboard' => 'Back to episode dashboard', 'post' => 'Your announcement post', 'post_hint' => "Write a message to announce the publication of your episode. The message will be broadcasted to all your followers in the fediverse and be featured in your podcast's homepage.", - 'message_placeholder' => 'Write your message…', - 'publication_date' => 'Publication date', + 'message_placeholder' => 'Schrijf uw bericht…', + 'publication_date' => 'Publicatiedatum', 'publication_method' => [ - 'now' => 'Now', - 'schedule' => 'Schedule', - 'with_podcast' => 'Publish alongside podcast', + 'now' => 'Nu', + 'schedule' => 'Plannen', + 'with_podcast' => 'Publiceer samen met podcast', ], - 'scheduled_publication_date' => 'Scheduled publication date', - 'scheduled_publication_date_clear' => 'Clear publication date', + 'scheduled_publication_date' => 'Gepland publicatiedatum', + 'scheduled_publication_date_clear' => 'Publicatiedatum wissen', 'scheduled_publication_date_hint' => 'You can schedule the episode release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', - 'submit' => 'Publish', - 'submit_edit' => 'Edit publication', - 'cancel_publication' => 'Cancel publication', + 'submit' => 'Publiceren', + 'submit_edit' => 'Publicatie bewerken', + 'cancel_publication' => 'Publicatie annuleren', 'message_warning' => 'You did not write a message for your announcement post!', 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your episode.', - 'message_warning_submit' => 'Publish anyways', + 'message_warning_submit' => 'Toch publiceren', ], 'publish_date_edit_form' => [ - 'new_publication_date' => 'New publication date', + 'new_publication_date' => 'Nieuwe publicatiedatum', 'new_publication_date_hint' => 'Must be set to a past date.', - 'submit' => 'Edit publication date', + 'submit' => 'Publicatiedatum bewerken', ], 'unpublish_form' => [ 'disclaimer' => "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", 'understand' => 'I understand, I want to unpublish the episode', - 'submit' => 'Unpublish', + 'submit' => 'Publicatie ongedaan maken', ], 'delete_form' => [ 'disclaimer' => "Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.", 'understand' => 'I understand, I want to delete the episode', - 'submit' => 'Delete', + 'submit' => 'Verwijderen', ], 'embed' => [ 'title' => 'Embeddable player', 'label' => 'Pick a theme color, copy the embeddable player to clipboard, then paste it on your website.', 'clipboard_iframe' => 'Copy embeddable player to clipboard', - 'clipboard_url' => 'Copy address to clipboard', - 'dark' => 'Dark', + 'clipboard_url' => 'Kopieer adres naar klembord', + 'dark' => 'Donker', 'dark-transparent' => 'Dark transparent', - 'light' => 'Light', + 'light' => 'Licht', 'light-transparent' => 'Light transparent', ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Voorbeeld', + ], ]; diff --git a/modules/Admin/Language/nl/EpisodeNavigation.php b/modules/Admin/Language/nl/EpisodeNavigation.php index 1406e301..34577f82 100644 --- a/modules/Admin/Language/nl/EpisodeNavigation.php +++ b/modules/Admin/Language/nl/EpisodeNavigation.php @@ -11,13 +11,13 @@ declare(strict_types=1); return [ 'go_to_page' => 'View episode page', 'dashboard' => 'Episode dashboard', - 'episode-view' => 'Home', - 'episode-edit' => 'Edit episode', - 'episode-persons-manage' => 'Manage persons', + 'episode-view' => 'Hoofdpagina', + 'episode-edit' => 'Aflevering bewerken', + 'episode-persons-manage' => 'Personen beheren', 'embed-add' => 'Embeddable player', 'clips' => 'Clips', - 'video-clips-list' => 'Video clips', - 'video-clips-create' => 'New video clip', + 'video-clips-list' => 'Videoclips', + 'video-clips-create' => 'Nieuwe videoclip', 'soundbites-list' => 'Soundbites', 'soundbites-create' => 'New soundbite', ]; diff --git a/modules/Admin/Language/nl/Fediverse.php b/modules/Admin/Language/nl/Fediverse.php index 0e4ca66d..2ce69a02 100644 --- a/modules/Admin/Language/nl/Fediverse.php +++ b/modules/Admin/Language/nl/Fediverse.php @@ -10,23 +10,23 @@ declare(strict_types=1); return [ 'messages' => [ - 'actorNotFound' => 'The account could not be found!', - 'blockActorSuccess' => '{actor} has been blocked!', - 'unblockActorSuccess' => 'Actor has been unblocked!', - 'blockDomainSuccess' => '{domain} has been blocked!', - 'unblockDomainSuccess' => '{domain} has been unblocked!', + 'actorNotFound' => 'Het account werd niet gevonden!', + 'blockActorSuccess' => '{actor} is geblokkeerd!', + 'unblockActorSuccess' => 'Acteur is gedeblokkeerd!', + 'blockDomainSuccess' => '{domain} is geblokkeerd!', + 'unblockDomainSuccess' => '{domain} is gedeblokkeerd!', ], - 'blocked_actors' => 'Blocked accounts', - 'blocked_domains' => 'Blocked domains', + 'blocked_actors' => 'Geblokkeerde accounts', + 'blocked_domains' => 'Geblokkeerde domeinen', 'block_lists_form' => [ 'handle' => 'Account handle', 'handle_hint' => 'Input @username@domain account.', - 'domain' => 'Domain name', + 'domain' => 'Domeinnaam', 'submit' => 'Block!', ], 'list' => [ 'actor' => 'Account', - 'domain' => 'Domain name', - 'unblock' => 'Unblock', + 'domain' => 'Domeinnaam', + 'unblock' => 'Deblokkeren', ], ]; diff --git a/modules/Admin/Language/nl/Home.php b/modules/Admin/Language/nl/Home.php index 3ff4c04d..8620f125 100644 --- a/modules/Admin/Language/nl/Home.php +++ b/modules/Admin/Language/nl/Home.php @@ -9,6 +9,6 @@ declare(strict_types=1); */ return [ - 'all_podcasts' => 'All podcasts', - 'no_podcast' => 'No podcast found', + 'all_podcasts' => 'Alle podcasts', + 'no_podcast' => 'Geen podcast gevonden', ]; diff --git a/modules/Admin/Language/nl/Install.php b/modules/Admin/Language/nl/Install.php index 36e373a2..cfd92f3f 100644 --- a/modules/Admin/Language/nl/Install.php +++ b/modules/Admin/Language/nl/Install.php @@ -9,12 +9,12 @@ declare(strict_types=1); */ return [ - 'manual_config' => 'Manual configuration', + 'manual_config' => 'Handmatige configuratie', 'manual_config_subtitle' => 'Create a `.env` file with your settings and refresh the page to continue installation.', 'form' => [ 'instance_config' => 'Instance configuration', - 'hostname' => 'Hostname', + 'hostname' => 'Hostnaam', 'media_base_url' => 'Media base URL', 'media_base_url_hint' => 'If you use a CDN and/or an external analytics service, you may set them here.', @@ -28,7 +28,7 @@ return [ 'database_config_hint' => 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', 'db_hostname' => 'Database hostname', - 'db_name' => 'Database name', + 'db_name' => 'Databasenaam', 'db_username' => 'Database username', 'db_password' => 'Database password', 'db_prefix' => 'Database prefix', @@ -39,16 +39,16 @@ return [ 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', 'cache_handler' => 'Cache handler', 'cacheHandlerOptions' => [ - 'file' => 'File', + 'file' => 'Bestand', 'redis' => 'Redis', 'predis' => 'Predis', ], - 'next' => 'Next', - 'submit' => 'Finish install', + 'next' => 'Volgende', + 'submit' => 'Installatie voltooien', 'create_superadmin' => 'Create your superadmin account', - 'email' => 'Email', - 'username' => 'Username', - 'password' => 'Password', + 'email' => 'E-mail', + 'username' => 'Gebruikersnaam', + 'password' => 'Wachtwoord', ], 'messages' => [ 'createSuperAdminSuccess' => diff --git a/modules/Admin/Language/nl/Navigation.php b/modules/Admin/Language/nl/Navigation.php index 68d4609d..62f09b7c 100644 --- a/modules/Admin/Language/nl/Navigation.php +++ b/modules/Admin/Language/nl/Navigation.php @@ -9,33 +9,36 @@ declare(strict_types=1); */ return [ - 'toggle_sidebar' => 'Toggle sidebar', - 'go_to_website' => 'Go to website', + 'toggle_sidebar' => 'Zijbalk tonen/verbergen', + 'go_to_website' => 'Ga naar website', 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Niet geautoriseerd', 'dashboard' => 'Dashboard', - 'admin' => 'Home', + 'admin' => 'Hoofdpagina', 'podcasts' => 'Podcasts', - 'podcast-list' => 'All podcasts', - 'podcast-create' => 'New podcast', - 'podcast-import' => 'Import a podcast', - 'persons' => 'Persons', - 'person-list' => 'All persons', - 'person-create' => 'New person', + 'podcast-list' => 'Alle podcasts', + 'podcast-create' => 'Nieuwe podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Importeer een podcast', + 'persons' => 'Personen', + 'person-list' => 'Alle personen', + 'person-create' => 'Nieuwe persoon', 'fediverse' => 'Fediverse', - 'fediverse-blocked-actors' => 'Blocked accounts', - 'fediverse-blocked-domains' => 'Blocked domains', - 'users' => 'Users', - 'user-list' => 'All users', - 'user-create' => 'New user', - 'pages' => 'Pages', - 'page-list' => 'All pages', - 'page-create' => 'New Page', - 'settings' => 'Settings', - 'settings-general' => 'General', - 'settings-theme' => 'Theme', + 'fediverse-blocked-actors' => 'Geblokkeerde accounts', + 'fediverse-blocked-domains' => 'Geblokkeerde domeinen', + 'users' => 'Gebruikers', + 'user-list' => 'Alle gebruikers', + 'user-create' => 'Nieuwe gebruiker', + 'pages' => 'Pagina\'s', + 'page-list' => 'Alle pagina\'s', + 'page-create' => 'Nieuwe pagina', + 'settings' => 'Instellingen', + 'settings-general' => 'Algemeen', + 'settings-theme' => 'Thema', + 'admin-about' => 'Over', 'account' => [ - 'my-account' => 'My account', - 'change-password' => 'Change password', - 'logout' => 'Logout', + 'my-account' => 'Mijn account', + 'change-password' => 'Wachtwoord wijzigen', + 'logout' => 'Uitloggen', ], ]; diff --git a/modules/Admin/Language/nl/Notifications.php b/modules/Admin/Language/nl/Notifications.php index 2b139d51..76d9697b 100644 --- a/modules/Admin/Language/nl/Notifications.php +++ b/modules/Admin/Language/nl/Notifications.php @@ -9,11 +9,11 @@ declare(strict_types=1); */ return [ - 'title' => 'Notifications', - 'reply' => '{actor_username} replied to your post', - 'favourite' => '{actor_username} favourited your post', - 'reblog' => '{actor_username} shared your post', - 'follow' => '{actor_username} started following you', - 'no_notifications' => 'No notifications', - 'mark_all_as_read' => 'Mark all as read', + 'title' => 'Meldingen', + 'reply' => '{actor_username} heeft op uw bericht gereageerd', + 'favourite' => '{actor_username} heeft je post als favoriet gemarkeerd', + 'reblog' => '{actor_username} heeft je bericht gedeeld', + 'follow' => '{actor_username} volgt je nu', + 'no_notifications' => 'Geen meldingen', + 'mark_all_as_read' => 'Alles als gelezen markeren', ]; diff --git a/modules/Admin/Language/nl/Page.php b/modules/Admin/Language/nl/Page.php index b6f49de5..566bbd92 100644 --- a/modules/Admin/Language/nl/Page.php +++ b/modules/Admin/Language/nl/Page.php @@ -9,22 +9,22 @@ declare(strict_types=1); */ return [ - 'back_to_home' => 'Back to home', - 'page' => 'Page', - 'all_pages' => 'All pages', - 'create' => 'New page', - 'go_to_page' => 'Go to page', - 'edit' => 'Edit page', - 'delete' => 'Delete page', + 'back_to_home' => 'Terug naar de hoofdpagina', + 'page' => 'Pagina', + 'all_pages' => 'Alle pagina\'s', + 'create' => 'Nieuwe pagina', + 'go_to_page' => 'Ga naar pagina', + 'edit' => 'Pagina bewerken', + 'delete' => 'Pagina verwijderen', 'form' => [ - 'title' => 'Title', + 'title' => 'Titel', 'permalink' => 'Permalink', - 'content' => 'Content', - 'submit_create' => 'Create page', - 'submit_edit' => 'Save', + 'content' => 'Inhoud', + 'submit_create' => 'Pagina aanmaken', + 'submit_edit' => 'Opslaan', ], 'messages' => [ - 'createSuccess' => 'The page “{pageTitle}” was created successfully!', - 'editSuccess' => 'The page was successfully updated!', + 'createSuccess' => 'De pagina "{pageTitle}" is succesvol aangemaakt!', + 'editSuccess' => 'De pagina is succesvol bijgewerkt!', ], ]; diff --git a/modules/Admin/Language/nl/Pager.php b/modules/Admin/Language/nl/Pager.php index e25ee638..210448d8 100644 --- a/modules/Admin/Language/nl/Pager.php +++ b/modules/Admin/Language/nl/Pager.php @@ -9,13 +9,13 @@ declare(strict_types=1); */ return [ - 'pageNavigation' => 'Page navigation', - 'first' => 'First', - 'previous' => 'Previous', - 'next' => 'Next', - 'last' => 'Last', - 'older' => 'Older', - 'newer' => 'Newer', + 'pageNavigation' => 'Paginanavigatie', + 'first' => 'Eerste', + 'previous' => 'Vorige', + 'next' => 'Volgende', + 'last' => 'Laatste', + 'older' => 'Ouder', + 'newer' => 'Nieuwer', 'invalidTemplate' => '{0} is not a valid Pager template.', 'invalidPaginationGroup' => '{0} is not a valid Pagination group.', ]; diff --git a/modules/Admin/Language/nl/Person.php b/modules/Admin/Language/nl/Person.php index a652be9f..19a25696 100644 --- a/modules/Admin/Language/nl/Person.php +++ b/modules/Admin/Language/nl/Person.php @@ -9,57 +9,57 @@ declare(strict_types=1); */ return [ - 'persons' => 'Persons', - 'all_persons' => 'All persons', - 'no_person' => 'Nobody found!', - 'create' => 'Create a person', - 'view' => 'View person', - 'edit' => 'Edit person', - 'delete' => 'Delete person', + 'persons' => 'Personen', + 'all_persons' => 'Alle personen', + 'no_person' => 'Niemand gevonden!', + 'create' => 'Een persoon aanmaken', + 'view' => 'Persoon weergeven', + 'edit' => 'Persoon bewerken', + 'delete' => 'Persoon verwijderen', 'messages' => [ - 'createSuccess' => 'Person has been successfully created!', - 'editSuccess' => 'Person has been successfully updated!', - 'deleteSuccess' => 'Person has been removed!', + 'createSuccess' => 'Persoon is succesvol aangemaakt!', + 'editSuccess' => 'Persoon is succesvol bijgewerkt!', + 'deleteSuccess' => 'Persoon is verwijderd!', ], 'form' => [ 'avatar' => 'Avatar', 'avatar_size_hint' => 'Avatar must be squared and at least 400px wide and tall.', - 'full_name' => 'Full name', - 'full_name_hint' => 'This is the full name or alias of the person.', - 'unique_name' => 'Unique name', - 'unique_name_hint' => 'Used for URLs', - 'information_url' => 'Information URL', + 'full_name' => 'Volledige naam', + 'full_name_hint' => 'Dit is de volledige naam of alias van de persoon.', + 'unique_name' => 'Unieke naam', + 'unique_name_hint' => 'Gebruikt voor URLs', + 'information_url' => 'Informatie URL', 'information_url_hint' => 'Url to a relevant resource of information about the person, such as a homepage or third-party profile platform.', - 'submit_create' => 'Create person', - 'submit_edit' => 'Save person', + 'submit_create' => 'Persoon aanmaken', + 'submit_edit' => 'Persoon opslaan', ], 'podcast_form' => [ - 'title' => 'Manage persons', - 'add_section_title' => 'Add persons to this podcast', - 'add_section_subtitle' => 'You may pick several persons and roles.', - 'persons' => 'Persons', + 'title' => 'Personen beheren', + 'add_section_title' => 'Voeg personen toe aan deze podcast', + 'add_section_subtitle' => 'U kunt meerdere personen en rollen kiezen.', + 'persons' => 'Personen', 'persons_hint' => 'You may select one or several persons with the same roles. You need to create the persons first.', - 'roles' => 'Roles', + 'roles' => 'Rollen', 'roles_hint' => 'You may select none, one or several roles for a person.', 'submit_add' => 'Add person(s)', - 'remove' => 'Remove', + 'remove' => 'Verwijderen', ], 'episode_form' => [ - 'title' => 'Manage persons', - 'add_section_title' => 'Add persons to this episode', - 'add_section_subtitle' => 'You may pick several persons and roles.', - 'persons' => 'Persons', + 'title' => 'Personen beheren', + 'add_section_title' => 'Voeg personen toe aan deze aflevering', + 'add_section_subtitle' => 'U kunt meerdere personen en rollen kiezen.', + 'persons' => 'Personen', 'persons_hint' => 'You may select one or several persons with the same roles. You need to create the persons first.', - 'roles' => 'Roles', + 'roles' => 'Rollen', 'roles_hint' => 'You may select none, one or several roles for a person.', 'submit_add' => 'Add person(s)', - 'remove' => 'Remove', + 'remove' => 'Verwijderen', ], 'credits' => 'Credits', ]; diff --git a/modules/Admin/Language/nl/Platforms.php b/modules/Admin/Language/nl/Platforms.php index ab17d599..67f69651 100644 --- a/modules/Admin/Language/nl/Platforms.php +++ b/modules/Admin/Language/nl/Platforms.php @@ -9,13 +9,26 @@ declare(strict_types=1); */ return [ - 'title' => 'Platforms', - 'home_url' => 'Go to {platformName} website', - 'submit_url' => 'Submit your podcast on {platformName}', + 'title' => [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Sociale netwerken', + 'funding' => 'Funding links', + ], + 'website' => 'Website', + 'home_url' => 'Ga naar de {platformName} website', + 'register' => 'Registreren', + 'submit_url' => 'Dien jouw podcast in op {platformName}', + 'your_link' => 'Jouw link', + 'your_id' => [ + 'podcasting' => 'Jouw ID', + 'social' => 'Jouw ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', 'visible' => 'Display in podcast homepage?', 'on_embed' => 'Display on embeddable player?', - 'remove' => 'Remove {platformName}', - 'submit' => 'Save', + 'remove' => '{platformName} verwijderen', + 'submit' => 'Opslaan', 'messages' => [ 'updateSuccess' => 'Platform links have been successfully updated!', 'removeLinkSuccess' => 'The platform link has been removed.', diff --git a/modules/Admin/Language/nl/Podcast.php b/modules/Admin/Language/nl/Podcast.php index 426b763b..12cf5f2a 100644 --- a/modules/Admin/Language/nl/Podcast.php +++ b/modules/Admin/Language/nl/Podcast.php @@ -9,27 +9,29 @@ declare(strict_types=1); */ return [ - 'all_podcasts' => 'All podcasts', - 'no_podcast' => 'No podcast found!', - 'create' => 'Create podcast', - 'import' => 'Import podcast', - 'new_episode' => 'New Episode', - 'view' => 'View podcast', - 'edit' => 'Edit podcast', - 'publish' => 'Publish podcast', - 'publish_edit' => 'Edit publication', - 'delete' => 'Delete podcast', - 'see_episodes' => 'See episodes', - 'see_contributors' => 'See contributors', - 'go_to_page' => 'Go to page', - 'latest_episodes' => 'Latest episodes', - 'see_all_episodes' => 'See all episodes', - 'draft' => 'Draft', + 'all_podcasts' => 'Alle podcasts', + 'no_podcast' => 'Geen podcast gevonden!', + 'create' => 'Podcast aanmaken', + 'import' => 'Podcast importeren', + 'all_imports' => 'Podcast imports', + 'new_episode' => 'Nieuwe aflevering', + 'view' => 'Podcast bekijken', + 'edit' => 'Podcast bewerken', + 'publish' => 'Podcast publiceren', + 'publish_edit' => 'Publicatie bewerken', + 'delete' => 'Podcast verwijderen', + 'see_episodes' => 'Afleveringen bekijken', + 'see_contributors' => 'Bekijk bijdragers', + 'monetization_other' => 'Other monetization', + 'go_to_page' => 'Ga naar pagina', + 'latest_episodes' => 'Laatste afleveringen', + 'see_all_episodes' => 'Toon alle afleveringen', + 'draft' => 'Concept', 'messages' => [ - 'createSuccess' => 'Podcast successfully created!', - 'editSuccess' => 'Podcast has been successfully updated!', - 'importSuccess' => 'Podcast has been successfully imported!', - 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', + 'createSuccess' => 'Podcast succesvol aangemaakt!', + 'editSuccess' => 'Podcast is succesvol bijgewerkt!', + 'importSuccess' => 'Podcast is succesvol geïmporteerd!', + 'deleteSuccess' => 'Podcast @{podcast_handle} succesvol verwijderd!', 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, cover {cover} banner {banner} @@ -47,69 +49,87 @@ return [ one {# episode was} other {# episodes were} } added to the podcast!', - 'podcastFeedUpToDate' => 'Podcast is already up to date.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', - 'publishError' => 'This podcast is either already published or scheduled for publication.', - 'publishEditError' => 'This podcast is not scheduled for publication.', + 'podcastFeedUpToDate' => 'Podcast is al up-to-date.', + 'publishError' => 'Deze podcast is al gepubliceerd of gepland voor publicatie.', + 'publishEditError' => 'Deze podcast is niet gepland voor publicatie.', 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', 'scheduleDateError' => 'Schedule date must be set!', ], 'form' => [ 'identity_section_title' => 'Podcast identity', 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + 'cover' => 'Podcast cover', 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', 'banner' => 'Podcast banner', 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', 'banner_delete' => 'Delete podcast banner', - 'title' => 'Title', + 'title' => 'Titel', 'handle' => 'Handle', 'handle_hint' => 'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.', 'type' => [ - 'label' => 'Type', + 'label' => 'Soort', 'episodic' => 'Episodic', 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', 'serial' => 'Serial', - 'serial_hint' => 'If episodes are intended to be consumed in sequential order. The oldest episodes will be presented first.', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', ], - 'description' => 'Description', - 'classification_section_title' => 'Classification', + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + ], + 'description' => 'Omschrijving', + 'classification_section_title' => 'Classificatie', 'classification_section_subtitle' => 'These fields will impact your audience and competition.', - 'language' => 'Language', - 'category' => 'Category', - 'category_placeholder' => 'Select a category…', - 'other_categories' => 'Other categories', + 'language' => 'Taal', + 'category' => 'Categorie', + 'category_placeholder' => 'Selecteer een categorie…', + 'other_categories' => 'Andere categorieën', 'parental_advisory' => [ - 'label' => 'Parental advisory', - 'hint' => 'Does it contain explicit content?', - 'undefined' => 'undefined', + 'label' => 'Ouderlijk advies', + 'hint' => 'Bevat de aflevering de expliciete inhoud?', + 'undefined' => 'niet gedefineerd', 'clean' => 'Clean', - 'explicit' => 'Explicit', + 'explicit' => 'Expliciet', ], - 'author_section_title' => 'Author', - 'author_section_subtitle' => 'Who is managing the podcast?', - 'owner_name' => 'Owner name', + 'author_section_title' => 'Auteur', + 'author_section_subtitle' => 'Wie beheert de podcast?', + 'owner_name' => 'Naam eigenaar', 'owner_name_hint' => 'For administrative use only. Visible in the public RSS feed.', 'owner_email' => 'Owner email', 'owner_email_hint' => 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', - 'publisher' => 'Publisher', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', + 'publisher' => 'Uitgever', 'publisher_hint' => 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', - 'copyright' => 'Copyright', - 'location_section_title' => 'Location', + 'copyright' => 'Auteursrecht', + 'location_section_title' => 'Locatie', 'location_section_subtitle' => 'What place is this podcast about?', 'location_name' => 'Location name or address', 'location_name_hint' => 'This can be a real place or fictional', 'monetization_section_title' => 'Monetization', 'monetization_section_subtitle' => - 'Earn money thanks to your audience.', + 'Verdien geld dankzij uw publiek.', 'premium' => 'Premium', 'premium_by_default' => 'Episodes must be set as premium by default', 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', 'payment_pointer' => 'Payment Pointer for Web Monetization', 'payment_pointer_hint' => 'This is your where you will receive money thanks to Web Monetization', @@ -118,19 +138,19 @@ return [ 'If you need RSS tags that Castopod does not handle, set them here.', 'custom_rss' => 'Custom RSS tags for the podcast', 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', 'new_feed_url' => 'New feed URL', 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', - 'partnership' => 'Partnership', + 'partnership' => 'Samenwerking', 'partner_id' => 'ID', 'partner_link_url' => 'Link URL', 'partner_image_url' => 'Image URL', 'partner_id_hint' => 'Your own partner ID', 'partner_link_url_hint' => 'The generic partner link address', 'partner_image_url_hint' => 'The generic partner image address', - 'status_section_title' => 'Status', 'block' => 'Podcast should be hidden from public catalogues', 'block_hint' => 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', @@ -138,42 +158,42 @@ return [ 'lock' => 'Prevent podcast from being copied', 'lock_hint' => 'The purpose is to tell other podcast platforms whether they are allowed to import this feed. A value of yes means that any attempt to import this feed into a new platform should be rejected.', - 'submit_create' => 'Create podcast', - 'submit_edit' => 'Save podcast', + 'submit_create' => 'Podcast aanmaken', + 'submit_edit' => 'Podcast opslaan', ], 'category_options' => [ - 'uncategorized' => 'uncategorized', - 'arts' => 'Arts', - 'business' => 'Business', - 'comedy' => 'Comedy', - 'education' => 'Education', - 'fiction' => 'Fiction', + 'uncategorized' => 'niet gecategoriseerd', + 'arts' => 'Kunsten', + 'business' => 'Zakelijk', + 'comedy' => 'Komedie', + 'education' => 'Educatie', + 'fiction' => 'Fictie', 'government' => 'Government', - 'health_and_fitness' => 'Health & Fitness', - 'history' => 'History', - 'kids_and_family' => 'Kids & Family', - 'leisure' => 'Leisure', - 'music' => 'Music', - 'news' => 'News', - 'religion_and_spirituality' => 'Religion & Spirituality', - 'science' => 'Science', - 'society_and_culture' => 'Society & Culture', - 'sports' => 'Sports', - 'technology' => 'Technology', + 'health_and_fitness' => 'Gezondheid & Fitness', + 'history' => 'Geschiedenis', + 'kids_and_family' => 'Kinderen & Familie', + 'leisure' => 'Vrije tijd', + 'music' => 'Muziek', + 'news' => 'Nieuws', + 'religion_and_spirituality' => 'Geloof & Spiritualiteit', + 'science' => 'Wetenschap', + 'society_and_culture' => 'Maatschappij & Cultuur', + 'sports' => 'Sporten', + 'technology' => 'Technologie', 'true_crime' => 'True Crime', 'tv_and_film' => 'TV & Film', - 'books' => 'Books', - 'design' => 'Design', + 'books' => 'Boeken', + 'design' => 'Ontwerp', 'fashion_and_beauty' => 'Fashion & Beauty', - 'food' => 'Food', - 'performing_arts' => 'Performing Arts', - 'visual_arts' => 'Visual Arts', - 'careers' => 'Careers', - 'entrepreneurship' => 'Entrepreneurship', - 'investing' => 'Investing', - 'management' => 'Management', + 'food' => 'Voeding', + 'performing_arts' => 'Podiumkunsten', + 'visual_arts' => 'Beeldende kunsten', + 'careers' => 'Carrières', + 'entrepreneurship' => 'Ondernemerschap', + 'investing' => 'Investeren', + 'management' => 'Beheer', 'marketing' => 'Marketing', - 'non_profit' => 'Non-Profit', + 'non_profit' => 'Non-profit', 'comedy_interviews' => 'Comedy Interviews', 'improv' => 'Improv', 'stand_up' => 'Stand-Up', @@ -210,101 +230,101 @@ return [ 'entertainment_news' => 'Entertainment News', 'news_commentary' => 'News Commentary', 'politics' => 'Politics', - 'sports_news' => 'Sports News', + 'sports_news' => 'Sportnieuws', 'tech_news' => 'Tech News', - 'buddhism' => 'Buddhism', - 'christianity' => 'Christianity', - 'hinduism' => 'Hinduism', + 'buddhism' => 'Boeddhisme', + 'christianity' => 'Christendom', + 'hinduism' => 'Hindoeïsme', 'islam' => 'Islam', - 'judaism' => 'Judaism', - 'religion' => 'Religion', - 'spirituality' => 'Spirituality', - 'astronomy' => 'Astronomy', - 'chemistry' => 'Chemistry', - 'earth_sciences' => 'Earth Sciences', - 'life_sciences' => 'Life Sciences', - 'mathematics' => 'Mathematics', - 'natural_sciences' => 'Natural Sciences', - 'nature' => 'Nature', - 'physics' => 'Physics', - 'social_sciences' => 'Social Sciences', + 'judaism' => 'Jodendom', + 'religion' => 'Religie', + 'spirituality' => 'Spiritualiteit', + 'astronomy' => 'Astronomie', + 'chemistry' => 'Chemie', + 'earth_sciences' => 'Aardwetenschappen', + 'life_sciences' => 'Levenswetenschappen', + 'mathematics' => 'Wiskunde', + 'natural_sciences' => 'Natuurwetenschappen', + 'nature' => 'Natuur', + 'physics' => 'Fysica', + 'social_sciences' => 'Sociale wetenschappen', 'documentary' => 'Documentary', - 'personal_journals' => 'Personal Journals', - 'philosophy' => 'Philosophy', - 'places_and_travel' => 'Places & Travel', - 'relationships' => 'Relationships', - 'baseball' => 'Baseball', - 'basketball' => 'Basketball', + 'personal_journals' => 'Persoonlijke dagboeken', + 'philosophy' => 'Filosofie', + 'places_and_travel' => 'Plaatsen & Reizen', + 'relationships' => 'Relaties', + 'baseball' => 'Honkbal', + 'basketball' => 'Basketbal', 'cricket' => 'Cricket', 'fantasy_sports' => 'Fantasy Sports', - 'football' => 'Football', + 'football' => 'Voetbal', 'golf' => 'Golf', 'hockey' => 'Hockey', 'rugby' => 'Rugby', - 'running' => 'Running', - 'soccer' => 'Soccer', - 'swimming' => 'Swimming', + 'running' => 'Rennen', + 'soccer' => 'Voetbal', + 'swimming' => 'Zwemmen', 'tennis' => 'Tennis', - 'volleyball' => 'Volleyball', + 'volleyball' => 'Volleybal', 'wilderness' => 'Wilderness', - 'wrestling' => 'Wrestling', + 'wrestling' => 'Worstelen', 'after_shows' => 'After Shows', - 'film_history' => 'Film History', + 'film_history' => 'Filmgeschiedenis', 'film_interviews' => 'Film Interviews', 'film_reviews' => 'Film Reviews', - 'tv_reviews' => 'TV Reviews', + 'tv_reviews' => 'TV-beoordelingen', ], 'publish_form' => [ 'back_to_podcast_dashboard' => 'Back to podcast dashboard', 'post' => 'Your announcement post', 'post_hint' => "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", - 'message_placeholder' => 'Write your message…', - 'submit' => 'Publish', - 'publication_date' => 'Publication date', + 'message_placeholder' => 'Schrijf uw bericht…', + 'submit' => 'Publiceren', + 'publication_date' => 'Publicatiedatum', 'publication_method' => [ - 'now' => 'Now', - 'schedule' => 'Schedule', + 'now' => 'Nu', + 'schedule' => 'Plannen', ], - 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date' => 'Gepland publicatiedatum', 'scheduled_publication_date_hint' => 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', - 'submit_edit' => 'Edit publication', - 'cancel_publication' => 'Cancel publication', + 'submit_edit' => 'Publicatie bewerken', + 'cancel_publication' => 'Publicatie annuleren', 'message_warning' => 'You did not write a message for your announcement post!', 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', - 'message_warning_submit' => 'Publish anyway', + 'message_warning_submit' => 'Toch publiceren', ], 'publication_status_banner' => [ 'draft_mode' => 'draft mode', - 'not_published' => 'This podcast is not yet published.', - 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + 'not_published' => 'De podcast is nog niet gepubliceerd.', + 'scheduled' => 'Deze podcast is gepland voor publicatie op {publication_date}.', ], 'delete_form' => [ 'disclaimer' => "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", 'understand' => 'I understand, I want the podcast to be permanently deleted', - 'submit' => 'Delete', + 'submit' => 'Verwijderen', ], - 'by' => 'By {publisher}', - 'season' => 'Season {seasonNumber}', - 'list_of_episodes_year' => '{year} episodes ({episodeCount})', + 'by' => 'Door {publisher}', + 'season' => 'Seizoen {seasonNumber}', + 'list_of_episodes_year' => '{year} afleveringen ({episodeCount})', 'list_of_episodes_season' => - 'Season {seasonNumber} episodes ({episodeCount})', - 'no_episode' => 'No episode found!', - 'follow' => 'Follow', + 'Seizoen {seasonNumber} afleveringen ({episodeCount})', + 'no_episode' => 'Er zijn geen afleveringen gevonden!', + 'follow' => 'Volgen', 'followers' => '{numberOfFollowers, plural, - one {# follower} - other {# followers} + one {# volger} + other {# volgers} }', 'posts' => '{numberOfPosts, plural, - one {# post} - other {# posts} + one {# bericht} + other {# berichten} }', - 'activity' => 'Activity', - 'episodes' => 'Episodes', + 'activity' => 'Activiteit', + 'episodes' => 'Afleveringen', 'sponsor' => 'Sponsor', 'funding_links' => 'Funding links for {podcastTitle}', - 'find_on' => 'Find {podcastTitle} on', - 'listen_on' => 'Listen on', + 'find_on' => 'Vind {podcastTitle} op', + 'listen_on' => 'Beluister op', ]; diff --git a/modules/Admin/Language/nl/PodcastImport.php b/modules/Admin/Language/nl/PodcastImport.php deleted file mode 100644 index 7c3ef67d..00000000 --- a/modules/Admin/Language/nl/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'The podcast to import', - 'old_podcast_section_subtitle' => - 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', - 'imported_feed_url' => 'Feed URL', - 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', - 'new_podcast_section_title' => 'The new podcast', - 'advanced_params_section_title' => 'Advanced parameters', - 'advanced_params_section_subtitle' => - 'Keep the default values if you have no idea of what the fields are for.', - 'slug_field' => 'Field to be used to calculate episode slug', - 'description_field' => - 'Source field used for episode description / show notes', - 'force_renumber' => 'Force episodes renumbering', - 'force_renumber_hint' => - 'Use this if your podcast does not have episode numbers but wish to set them during import.', - 'season_number' => 'Season number', - 'season_number_hint' => - 'Use this if your podcast does not have a season number but wish to set one during import. Leave blank otherwise.', - 'max_episodes' => 'Maximum number of episodes to import', - 'max_episodes_hint' => 'Leave blank to import all episodes', - 'lock_import' => - 'This feed is protected. You cannot import it. If you are the owner, unprotect it on the origin platform.', - 'submit' => 'Import podcast', -]; diff --git a/modules/Admin/Language/nl/PodcastNavigation.php b/modules/Admin/Language/nl/PodcastNavigation.php index b4d7ddc0..6cbfcdbb 100644 --- a/modules/Admin/Language/nl/PodcastNavigation.php +++ b/modules/Admin/Language/nl/PodcastNavigation.php @@ -10,29 +10,33 @@ declare(strict_types=1); return [ 'go_to_page' => 'Go to podcast page', + 'rss_feed' => 'RSS feed', 'dashboard' => 'Podcast dashboard', - 'podcast-view' => 'Home', - 'podcast-edit' => 'Edit podcast', - 'podcast-persons-manage' => 'Manage persons', - 'episodes' => 'Episodes', - 'episode-list' => 'All episodes', - 'episode-create' => 'New episode', - 'analytics' => 'Analytics', + 'podcast-view' => 'Hoofdpagina', + 'podcast-edit' => 'Podcast bewerken', + 'podcast-persons-manage' => 'Personen beheren', + 'podcast-imports' => 'Podcast importeren', + 'podcast-imports-sync' => 'Synchroniseer feeds', + 'episodes' => 'Afleveringen', + 'episode-list' => 'Alle afleveringen', + 'episode-create' => 'Nieuwe aflevering', + 'analytics' => 'Statistieken', 'podcast-analytics' => 'Audience overview', 'podcast-analytics-webpages' => 'Web pages visits', - 'podcast-analytics-locations' => 'Locations', - 'podcast-analytics-unique-listeners' => 'Unique listeners', + 'podcast-analytics-locations' => 'Locaties', + 'podcast-analytics-unique-listeners' => 'Unieke luisteraars', 'podcast-analytics-players' => 'Players', - 'podcast-analytics-listening-time' => 'Listening time', + 'podcast-analytics-listening-time' => 'Luistertijd', 'podcast-analytics-time-periods' => 'Time periods', - 'premium' => 'Premium', - 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', - 'contributors' => 'Contributors', - 'contributor-list' => 'All contributors', - 'contributor-add' => 'Add contributor', - 'platforms' => 'External platforms', - 'platforms-podcasting' => 'Podcasting', - 'platforms-social' => 'Social networks', - 'platforms-funding' => 'Funding', + 'monetization' => 'Monetization', + 'subscription-list' => 'Alle abonnementen', + 'subscription-create' => 'Abonnement toevoegen', + 'contributors' => 'Bijdragers', + 'contributor-list' => 'Alle bijdragers', + 'contributor-add' => 'Bijdragers toevoegen', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', + 'platforms-social' => 'Sociale netwerken', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Andere', ]; diff --git a/modules/Admin/Language/nl/Settings.php b/modules/Admin/Language/nl/Settings.php index 4a70dcba..5ce212db 100644 --- a/modules/Admin/Language/nl/Settings.php +++ b/modules/Admin/Language/nl/Settings.php @@ -9,7 +9,7 @@ declare(strict_types=1); */ return [ - 'title' => 'General settings', + 'title' => 'Algemene instellingen', 'instance' => [ 'title' => 'Instance', 'site_icon' => 'Site icon', @@ -18,18 +18,18 @@ return [ 'site_icon_helper' => 'Icon must be squared and at least 512px wide and tall.', 'site_name' => 'Site name', 'site_description' => 'Site description', - 'submit' => 'Save', + 'submit' => 'Opslaan', 'editSuccess' => 'Instance has been updated successfully!', 'deleteIconSuccess' => 'Site icon has been remove successfully!', ], 'images' => [ - 'title' => 'Images', + 'title' => 'Afbeeldingen', 'subtitle' => 'Here you can regenerate all images based on the originals that were uploaded. To be used if you find that some images are missing. This task may take a while.', - 'regenerate' => 'Regenerate images', + 'regenerate' => 'Afbeeldingen regenereren', 'regenerationSuccess' => 'All images have been regenerated successfully!', ], 'housekeeping' => [ - 'title' => 'Housekeeping', + 'title' => 'Huishouden', 'subtitle' => 'Runs various housekeeping tasks. Use this feature if you ever encounter issues with media files or data integrity. These tasks may take a while.', 'reset_counts' => 'Reset counts', 'reset_counts_helper' => 'This option will recalculate and reset all data counts (number of followers, posts, comments, …).', @@ -43,16 +43,16 @@ return [ 'runSuccess' => 'Housekeeping has been run successfully!', ], 'theme' => [ - 'title' => 'Theme', - 'accent_section_title' => 'Accent color', - 'accent_section_subtitle' => 'Choose the color to determine the look and feel of all public pages.', - 'pine' => 'Pine', + 'title' => 'Thema', + 'accent_section_title' => 'Accentkleur', + 'accent_section_subtitle' => 'Kies de kleur om het uiterlijk van alle openbare pagina\'s te bepalen.', + 'pine' => 'Den', 'crimson' => 'Crimson', 'amber' => 'Amber', - 'lake' => 'Lake', + 'lake' => 'Meer', 'jacaranda' => 'Jacaranda', 'onyx' => 'Onyx', - 'submit' => 'Save', - 'setInstanceThemeSuccess' => 'Theme has been updated successfully!', + 'submit' => 'Opslaan', + 'setInstanceThemeSuccess' => 'Thema is succesvol bijgewerkt!', ], ]; diff --git a/modules/Admin/Language/nl/Soundbite.php b/modules/Admin/Language/nl/Soundbite.php index a3f828fe..0f67581e 100644 --- a/modules/Admin/Language/nl/Soundbite.php +++ b/modules/Admin/Language/nl/Soundbite.php @@ -20,8 +20,8 @@ return [ 'form' => [ 'title' => 'New soundbite', 'soundbite_title' => 'Soundbite title', - 'start_time' => 'Start at', - 'duration' => 'Duration', + 'start_time' => 'Begintijd', + 'duration' => 'Duur', 'submit' => 'Create soundbite', ], 'play' => 'Play soundbite', diff --git a/modules/Admin/Language/nl/User.php b/modules/Admin/Language/nl/User.php deleted file mode 100644 index 585d6799..00000000 --- a/modules/Admin/Language/nl/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', - 'ban' => 'Ban', - 'unban' => 'Unban', - 'delete' => 'Delete', - 'create' => 'New user', - 'view' => "{username}'s info", - 'all_users' => 'All users', - 'list' => [ - 'user' => 'User', - 'roles' => 'Roles', - 'banned' => 'Banned?', - ], - 'form' => [ - 'email' => 'Email', - 'username' => 'Username', - 'password' => 'Password', - 'new_password' => 'New Password', - 'roles' => 'Roles', - 'permissions' => 'Permissions', - 'submit_create' => 'Create user', - 'submit_edit' => 'Save', - 'submit_password_change' => 'Change!', - ], - 'roles' => [ - 'superadmin' => 'Super admin', - ], - 'messages' => [ - 'createSuccess' => - 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => - "{username}'s roles have been successfully updated.", - 'forcePassResetSuccess' => - '{username} will be prompted with a password reset upon next visit.', - 'banSuccess' => '{username} has been banned.', - 'unbanSuccess' => '{username} has been unbanned.', - 'editOwnerError' => - '{username} is the instance owner, you cannot edit its roles.', - 'banSuperAdminError' => - '{username} is a superadmin, one does not simply ban a superadmin…', - 'deleteSuperAdminError' => - '{username} is a superadmin, one does not simply delete a superadmin…', - 'deleteSuccess' => '{username} has been deleted.', - ], -]; diff --git a/modules/Admin/Language/nl/Validation.php b/modules/Admin/Language/nl/Validation.php index 750b1968..1e61ef35 100644 --- a/modules/Admin/Language/nl/Validation.php +++ b/modules/Admin/Language/nl/Validation.php @@ -12,7 +12,6 @@ return [ 'min_dims' => '{field} is either not an image, or it is not wide or tall enough.', 'is_image_ratio' => - '{field} is either not an image or not of the right ratio.', - 'validate_url' => - 'The {field} field must be a valid URL (eg. https://example.com/).', + '{field} is geen afbeelding of niet van de juiste verhouding.', + 'is_json' => '{field} contains invalid JSON.', ]; diff --git a/modules/Admin/Language/nl/VideoClip.php b/modules/Admin/Language/nl/VideoClip.php index 638de697..c277d850 100644 --- a/modules/Admin/Language/nl/VideoClip.php +++ b/modules/Admin/Language/nl/VideoClip.php @@ -10,16 +10,16 @@ declare(strict_types=1); return [ 'list' => [ - 'title' => 'Video clips', + 'title' => 'Videoclips', 'status' => [ 'label' => 'Status', - 'queued' => 'queued', + 'queued' => 'in wachtrij', 'queued_hint' => 'Clip is waiting to be processed.', - 'pending' => 'pending', + 'pending' => 'in behandeling', 'pending_hint' => 'Clip will be generated shortly.', - 'running' => 'running', + 'running' => 'actief', 'running_hint' => 'Clip is being generated.', - 'failed' => 'failed', + 'failed' => 'mislukt', 'failed_hint' => 'Clip could not be generated: script failure.', 'passed' => 'passed', 'passed_hint' => 'Clip was generated successfully!', @@ -40,23 +40,23 @@ return [ 'deleteSuccess' => 'Video clip has been successfully removed!', ], 'format' => [ - 'landscape' => 'Landscape', + 'landscape' => 'Liggend', 'portrait' => 'Portrait', - 'squared' => 'Squared', + 'squared' => 'Vierkant', ], 'form' => [ - 'title' => 'New video clip', + 'title' => 'Nieuwe videoclip', 'params_section_title' => 'Video clip parameters', 'clip_title' => 'Clip title', 'format' => [ - 'label' => 'Choose a format', + 'label' => 'Kies een formaat', 'landscape_hint' => 'With a 16:9 ratio, landscape videos are great for PeerTube, Youtube and Vimeo.', 'portrait_hint' => 'With a 9:16 ratio, portrait videos are great for TikTok, Youtube shorts and Instagram stories.', 'squared_hint' => 'With a 1:1 ratio, squared videos are great for Mastodon, Facebook, Twitter and LinkedIn.', ], - 'theme' => 'Select a theme', - 'start_time' => 'Start at', - 'duration' => 'Duration', + 'theme' => 'Selecteer een thema', + 'start_time' => 'Begintijd', + 'duration' => 'Duur', 'trim_start' => 'Trim start', 'trim_end' => 'Trim end', 'submit' => 'Create video clip', diff --git a/modules/Admin/Language/nn-NO/PodcastImport.php b/modules/Admin/Language/nn-NO/PodcastImport.php deleted file mode 100644 index 2ef0f072..00000000 --- a/modules/Admin/Language/nn-NO/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'Podkast å importera', - 'old_podcast_section_subtitle' => - 'Syt for at du har rettane til podkasten før du importerer han. Å kopiera og kringkasta ein podkast utan løyve er ulovleg og straffbart.', - 'imported_feed_url' => 'URL til straumen', - 'imported_feed_url_hint' => 'Straumen må vera i xml- eller rss-format.', - 'new_podcast_section_title' => 'Den nye podkasten', - 'advanced_params_section_title' => 'Avanserte innstilingar', - 'advanced_params_section_subtitle' => - 'Bruk standardverdiane viss du ikkje veit kva desse felta er til.', - 'slug_field' => 'Felt som skal brukast til å laga kortadressa til episoden', - 'description_field' => - 'Kjeldefelt som skal brukast for å skildra episoden og syna notat', - 'force_renumber' => 'Tving renummerering av episodane', - 'force_renumber_hint' => - 'Bruk dette viss podkasten din ikkje har episodenummer, men du vil laga nummer når du importerer.', - 'season_number' => 'Sesongnummer', - 'season_number_hint' => - 'Bruk dette viss podkasten din ikkje har eit sesongnummer, men du vil laga eit når du importerer. La stå tomt i andre tilfelle.', - 'max_episodes' => 'Makstal på episodar å importera', - 'max_episodes_hint' => 'La stå tomt for å importera alle episodane', - 'lock_import' => - 'Denne straumen er verna. Du kan ikkje importera han. Viss du er eigaren, må du ta bort vernet på den originale plattforma.', - 'submit' => 'Importer ein podkast', -]; diff --git a/modules/Admin/Language/nn-no/AboutCastopod.php b/modules/Admin/Language/nn-no/AboutCastopod.php new file mode 100644 index 00000000..cdcce825 --- /dev/null +++ b/modules/Admin/Language/nn-no/AboutCastopod.php @@ -0,0 +1,22 @@ + 'Om Castopod', + 'host_name' => 'Vertsnamn', + 'version' => 'Castopod-versjon', + 'php_version' => 'PHP-versjon', + 'os' => 'Operativsystem', + 'languages' => 'Språk', + 'update_database' => 'Oppdater databasen', + 'messages' => [ + 'databaseUpdateSuccess' => 'Databasen er oppdatert!', + ], +]; diff --git a/modules/Admin/Language/nn-NO/Breadcrumb.php b/modules/Admin/Language/nn-no/Breadcrumb.php similarity index 74% rename from modules/Admin/Language/nn-NO/Breadcrumb.php rename to modules/Admin/Language/nn-no/Breadcrumb.php index d59e34ef..422e71df 100644 --- a/modules/Admin/Language/nn-NO/Breadcrumb.php +++ b/modules/Admin/Language/nn-no/Breadcrumb.php @@ -14,29 +14,34 @@ return [ ->gateway => 'Heim', 'podcasts' => 'podkastar', 'episodes' => 'episodar', - 'subscriptions' => 'subscriptions', + 'subscriptions' => 'tingingar', 'contributors' => 'bidragsytarar', 'pages' => 'sider', 'settings' => 'innstillingar', 'theme' => 'bunad', + 'about' => 'om', 'add' => 'legg til', 'new' => 'ny', 'edit' => 'rediger', 'persons' => 'personar', 'publish' => 'legg ut', 'publish-edit' => 'rediger publiseringa', - 'publish-date-edit' => 'edit publication date', + 'publish-date-edit' => 'rediger publiseringsdato', 'unpublish' => 'avpubliser', 'delete' => 'slett', + 'remove' => 'fjern', 'fediverse' => 'fødiverset', - 'block-lists' => 'blokkeringslister', + 'blocked-actors' => 'blokkerte aktørar', + 'blocked-domains' => 'blokkerte domene', 'users' => 'brukarar', 'my-account' => 'kontoen min', 'change-password' => 'endre passord', - 'import' => 'importer straumar', + 'imports' => 'importar', + 'sync-feeds' => 'synkroniser straumar', 'platforms' => 'plattformer', 'social' => 'sosiale nettverk', 'funding' => 'finansiering', + 'monetization-other' => 'andre måtar å tena pengar på', 'analytics' => 'analysar', 'locations' => 'stader', 'webpages' => 'nettsider', @@ -47,6 +52,6 @@ return [ 'soundbites' => 'lydbetar', 'video-clips' => 'videoklypp', 'embed' => 'innbyggbar spelar', - 'notifications' => 'notifications', - 'suspend' => 'suspend', + 'notifications' => 'varslingar', + 'suspend' => 'utvis', ]; diff --git a/modules/Admin/Language/nn-NO/Charts.php b/modules/Admin/Language/nn-no/Charts.php similarity index 89% rename from modules/Admin/Language/nn-NO/Charts.php rename to modules/Admin/Language/nn-no/Charts.php index c0880f32..5890aa0d 100644 --- a/modules/Admin/Language/nn-NO/Charts.php +++ b/modules/Admin/Language/nn-no/Charts.php @@ -35,6 +35,7 @@ return [ 'by_weekday' => 'Etter vekedag (dei siste 60 dagane)', 'by_hour' => 'Etter tid på dagen (dei siste 60 dagane)', 'podcast_by_bandwidth' => 'Dagleg bandbreidde (i MB)', - 'total_storage_by_month' => 'Monthly storage (in MB)', - 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_storage_by_month' => 'Lagrinsgsplass kvar månad (i MB)', + 'total_bandwidth_by_month' => 'Brukt bandbreidd pr. månad (i MB)', + 'total_bandwidth_by_month_limit' => 'Avgrensa til {totalBandwidth} pr. månad', ]; diff --git a/modules/Admin/Language/nn-NO/Common.php b/modules/Admin/Language/nn-no/Common.php similarity index 92% rename from modules/Admin/Language/nn-NO/Common.php rename to modules/Admin/Language/nn-no/Common.php index db095fd9..f9678459 100644 --- a/modules/Admin/Language/nn-NO/Common.php +++ b/modules/Admin/Language/nn-no/Common.php @@ -40,12 +40,13 @@ return [ ], 'upload_file' => 'Last opp ei fil', 'remote_url' => 'Ekstern URL-adresse', + 'save' => 'Lagre', ], 'play_episode_button' => [ 'play' => 'Spel', 'playing' => 'Spelar', ], 'size_limit' => 'Maks storleik: {0}.', - 'choose_interact' => 'Choose how to interact', - 'view' => 'View', + 'choose_interact' => 'Vel korleis du vil samhandla', + 'view' => 'Vis', ]; diff --git a/modules/Admin/Language/nn-NO/Countries.php b/modules/Admin/Language/nn-no/Countries.php similarity index 100% rename from modules/Admin/Language/nn-NO/Countries.php rename to modules/Admin/Language/nn-no/Countries.php diff --git a/modules/Admin/Language/nn-no/Dashboard.php b/modules/Admin/Language/nn-no/Dashboard.php new file mode 100644 index 00000000..cf1ee11a --- /dev/null +++ b/modules/Admin/Language/nn-no/Dashboard.php @@ -0,0 +1,28 @@ + 'Styringspanel', + 'welcome_message' => 'Velkomen til styrarområdet!', + 'podcasts' => [ + 'title' => 'Podkastar', + 'not_found' => 'Ingen publiserte podkastar', + 'last_published' => 'Sist lagt ut {lastPublicationDate}', + ], + 'episodes' => [ + 'title' => 'Episodar', + 'not_found' => 'Ingen publisert episode', + 'last_published' => 'Sist lagt ut {lastPublicationDate}', + ], + 'storage' => [ + 'title' => 'Lagring', + 'subtitle' => '{totalUploaded} av {totalStorage}', + ], +]; diff --git a/modules/Admin/Language/nn-NO/Episode.php b/modules/Admin/Language/nn-no/Episode.php similarity index 68% rename from modules/Admin/Language/nn-NO/Episode.php rename to modules/Admin/Language/nn-no/Episode.php index 12a3672a..4e7bb526 100644 --- a/modules/Admin/Language/nn-NO/Episode.php +++ b/modules/Admin/Language/nn-no/Episode.php @@ -22,39 +22,41 @@ return [ 'all_podcast_episodes' => 'Alle podkast-episodane', 'back_to_podcast' => 'Gå tilbake til podkasten', 'edit' => 'Rediger', + 'preview' => 'Førehandsvising', 'publish' => 'Legg ut', 'publish_edit' => 'Rediger publiseringa', - 'publish_date_edit' => 'Edit publication date', + 'publish_date_edit' => 'Rediger publiseringsdatoen', 'unpublish' => 'Avpubliser', 'publish_error' => 'Episoden er allereie publisert.', 'publish_edit_error' => 'Episoden er allereie publisert.', 'publish_cancel_error' => 'Episoden er allereie publisert.', - 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', - 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', - 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'publish_date_edit_error' => 'Episoden er ikkje lagt ut enno, så du kan ikkje endra publiseringsdatoen.', + 'publish_date_edit_future_error' => 'Du kan berre setja publiseringsdatoen til ein tidlegare dato. Viss du vil endra planlagd publisering, kan du avpublisera episoden fyrst.', + 'publish_date_edit_success' => 'Publiseringsdatoen er endra.', 'unpublish_error' => 'Episoden er ikkje publisert.', 'delete' => 'Slett', 'go_to_page' => 'Gå til side', 'create' => 'Legg til ein episode', 'publication_status' => [ 'published' => 'Lagt ut', - 'with_podcast' => 'Published', + 'with_podcast' => 'Lagt ut', 'scheduled' => 'Planlagt', 'not_published' => 'Ikkje lagt ut', ], - 'with_podcast_hint' => 'To be published at the same time as the podcast', + 'with_podcast_hint' => 'Skal gjevast ut samstundes som podkasten', 'list' => [ 'search' => [ - 'placeholder' => 'Search for an episode', - 'clear' => 'Clear search', - 'submit' => 'Search', + 'placeholder' => 'Søk etter ein episode', + 'clear' => 'Tøm søket', + 'submit' => 'Søk', ], 'number_of_episodes' => '{numberOfEpisodes, plural, one {# episode} - other {# episodes} + other {# episodar} }', 'episode' => 'Episode', 'visibility' => 'Synlegheit', + 'downloads' => 'Nedlastingar', 'comments' => 'Kommentarar', 'actions' => 'Handlingar', ], @@ -62,31 +64,31 @@ return [ 'createSuccess' => 'Episoden er oppretta!', 'editSuccess' => 'Episoden er oppdatert!', 'publishSuccess' => '{publication_status, select, - published {Episode successfully published!} - scheduled {Episode publication successfully scheduled!} - with_podcast {This episode will be published at the same time as the podcast.} - other {This episode is not published.} + published {Episoden er lagt ut.} + scheduled {Episoden er planlagt lagt ut.} + with_podcast {Denne episoden blir lagt ut samstundes som podkasten.} + other {Denne episoden er ikkje lagt ut.} }', 'publishCancelSuccess' => 'Du har avbrote å leggja ut episoden.', - 'unpublishBeforeDeleteTip' => 'You must unpublish the episode before deleting it.', - 'scheduleDateError' => 'Schedule date must be set!', - 'deletePublishedEpisodeError' => 'Please unpublish the episode before deleting it.', - 'deleteSuccess' => 'Episode successfully deleted!', - 'deleteError' => 'Failed to delete episode {type, select, - transcript {transcript} - chapters {chapters} - image {cover} - audio {audio} + 'unpublishBeforeDeleteTip' => 'Du må avpublisera episoden før du slettar han.', + 'scheduleDateError' => 'Du må velja publiseringsdato.', + 'deletePublishedEpisodeError' => 'Du må avpublisera episoden før du slettar han.', + 'deleteSuccess' => 'Episoden er sletta.', + 'deleteError' => 'Greidde ikkje sletta {type, select, + transcript {transkripsjon} + chapters {kapittel} + image {omslag} + audio {lyd} other {media} - }.', - 'deleteFileError' => 'Failed to delete {type, select, - transcript {transcript} - chapters {chapters} - image {cover} - audio {audio} + } for episoden.', + 'deleteFileError' => 'Greidde ikkje sletta {type, select, + transcript {transkripsjons} + chapters {kapittel} + image {omslags} + audio {lyd} other {media} - } file {file_path}. You may manually remove it from your disk.', - 'sameSlugError' => 'An episode with the chosen slug already exists.', + }fila {file_key}. Du kan fjerna ho manuelt.', + 'sameSlugError' => 'Ei episode med denne kortadressa finst allereie.', ], 'form' => [ 'file_size_error' => @@ -97,7 +99,7 @@ return [ 'cover' => 'Episodeomslag', 'cover_hint' => 'Viss du ikkje bruker eige omslag, blir omslaget til podkasten brukt i staden.', - 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'cover_size_hint' => 'Omslaget må vera kvadratisk, og minst 1400pkt breitt og høgt.', 'title' => 'Tittel', 'title_hint' => 'Bør innehalda eit klårt og konsist episodenamn. Ikkje skriv inn nummer på episode eller sesong her.', @@ -114,7 +116,7 @@ return [ 'bonus_hint' => 'Ekstra innhald (til dømes bakominfo eller intervju med skodespelarane) eller innhald for å framheva ein annan serie', ], 'premium_title' => 'Premium', - 'premium' => 'Episode must be accessible to premium subscribers only', + 'premium' => 'Episoden må vera tilgjengeleg berre for premium-abonnentar', 'parental_advisory' => [ 'label' => 'Råd til foreldre', 'hint' => 'Inneheld episoden grov prat?', @@ -131,15 +133,15 @@ return [ 'Denne teksten ligg på slutten av kvar episodeskildring, og er ein god stad å ha lenker til td. sosiale nettverk.', 'additional_files_section_title' => 'Fleire filer', 'additional_files_section_subtitle' => - 'These files may be used by other platforms to provide better experience to your audience. See the {podcastNamespaceLink} for more information.', + 'Desse filene kan brukast av andre plattformer for å gje publikum ei betre oppleving. Sjå {podcastNamespaceLink} for meir informasjon.', 'location_section_title' => 'Stad', 'location_section_subtitle' => 'Kva stad handlar denne episoden om?', 'location_name' => 'Stadnamn eller adresse', 'location_name_hint' => 'Dette kan vera ein verkeleg eller oppdikta stad', 'transcript' => 'Transkribering (undertitlar eller teksting)', - 'transcript_hint' => 'Berre .srt.', + 'transcript_hint' => 'Berre .srt eller .vtt er lov.', 'transcript_download' => 'Last ned transkriberinga', - 'transcript_file' => 'Transkriberingsfil (.srt)', + 'transcript_file' => 'Transkriberingsfil (.srt eller .vtt)', 'transcript_remote_url' => 'Ekstern URL for teksting', 'transcript_file_delete' => 'Slett transkriberingsfila', 'chapters' => 'Kapittel', @@ -153,9 +155,9 @@ return [ 'Viss du treng RSS-merkelappar som Castopod ikkje handterer, kan du skriva dei inn her.', 'custom_rss' => 'Eigne RSS-merkelappar for episoden', 'custom_rss_hint' => 'Dette blir sett inn i ❬item❭-elementet.', - 'block' => 'Episode should be hidden from public catalogues', + 'block' => 'Episoden skal gøymast frå offentlege katalogar', 'block_hint' => - 'The episode show or hide status: toggling this on prevents the episode from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'Vis- eller gøym- status for episoden: Dersom du skrur på dette, hindrar det at episoden kjem opp i Apple podcasts, Google podcasts og andre tredjeparts-appar som hentar podkastar frå desse (men ingen garanti)', 'submit_create' => 'Lag episode', 'submit_edit' => 'Lagre episode', ], @@ -169,7 +171,7 @@ return [ 'publication_method' => [ 'now' => 'No', 'schedule' => 'Planlegg', - 'with_podcast' => 'Publish alongside podcast', + 'with_podcast' => 'Legg ut saman med podkast', ], 'scheduled_publication_date' => 'Planlagt publiseringsdato', 'scheduled_publication_date_clear' => 'Tøm publiseringsdatoen', @@ -183,21 +185,21 @@ return [ 'message_warning_submit' => 'Legg ut likevel', ], 'publish_date_edit_form' => [ - 'new_publication_date' => 'New publication date', - 'new_publication_date_hint' => 'Must be set to a past date.', - 'submit' => 'Edit publication date', + 'new_publication_date' => 'Ny publiseringsdato', + 'new_publication_date_hint' => 'Må vera ein dato i fortida.', + 'submit' => 'Rediger publiseringsdatoen', ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", + "Viss du avpubliserer episoden, slettar du alle kommentarar og innlegg til han, og fjernar han frå RSS-straumen til podkasten.", 'understand' => 'Eg forstår, eg vil avpublisera episoden', 'submit' => 'Avpubliser', ], 'delete_form' => [ 'disclaimer' => - "Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.", + "Viss du slettar episoden, slettar du òg alle tilknytte mediafiler, kommentarar, filmklypp og lydbetar.", 'understand' => 'Eg forstår, eg vil sletta episoden', - 'submit' => 'Delete', + 'submit' => 'Slett', ], 'embed' => [ 'title' => 'Innbyggbar spelar', @@ -210,4 +212,14 @@ return [ 'light' => 'Lys', 'light-transparent' => 'Lys gjennomsiktig', ], + 'publication_status_banner' => [ + 'draft_mode' => 'kladdemodus', + 'text' => '{publication_status, select, + published {Episoden er ikkje lagt ut enno.} + scheduled {Episoden er planlagt lagt ut på {publication_date}.} + with_podcast {Denne episoden blir lagt ut samstundes som podkasten.} + other {Denne episoden er ikkje lagt ut enno.} + }', + 'preview' => 'Førehandsvising', + ], ]; diff --git a/modules/Admin/Language/nn-NO/EpisodeNavigation.php b/modules/Admin/Language/nn-no/EpisodeNavigation.php similarity index 100% rename from modules/Admin/Language/nn-NO/EpisodeNavigation.php rename to modules/Admin/Language/nn-no/EpisodeNavigation.php diff --git a/modules/Admin/Language/nn-NO/Fediverse.php b/modules/Admin/Language/nn-no/Fediverse.php similarity index 100% rename from modules/Admin/Language/nn-NO/Fediverse.php rename to modules/Admin/Language/nn-no/Fediverse.php diff --git a/modules/Admin/Language/nn-NO/Home.php b/modules/Admin/Language/nn-no/Home.php similarity index 100% rename from modules/Admin/Language/nn-NO/Home.php rename to modules/Admin/Language/nn-no/Home.php diff --git a/modules/Admin/Language/nn-NO/Install.php b/modules/Admin/Language/nn-no/Install.php similarity index 100% rename from modules/Admin/Language/nn-NO/Install.php rename to modules/Admin/Language/nn-no/Install.php diff --git a/modules/Admin/Language/nn-NO/Navigation.php b/modules/Admin/Language/nn-no/Navigation.php similarity index 87% rename from modules/Admin/Language/nn-NO/Navigation.php rename to modules/Admin/Language/nn-no/Navigation.php index ce1b2185..d7101730 100644 --- a/modules/Admin/Language/nn-NO/Navigation.php +++ b/modules/Admin/Language/nn-no/Navigation.php @@ -12,12 +12,14 @@ return [ 'toggle_sidebar' => 'Vis/gøym sidepanelet', 'go_to_website' => 'Gå til nettsida', 'go_to_admin' => 'Gå til styringspanelet', + 'not-authorized' => 'Ikkje godkjent', 'dashboard' => 'Styringspanel', 'admin' => 'Heim', 'podcasts' => 'Podkastar', 'podcast-list' => 'Alle podkastar', 'podcast-create' => 'Ny podkast', - 'podcast-import' => 'Importer ein podkast', + 'all-podcast-imports' => 'Alle podkast-importar', + 'podcast-imports-add' => 'Importer ein podkast', 'persons' => 'Personar', 'person-list' => 'Alle personar', 'person-create' => 'Ny person', @@ -33,6 +35,7 @@ return [ 'settings' => 'Innstillingar', 'settings-general' => 'Generelt', 'settings-theme' => 'Bunad', + 'admin-about' => 'Om', 'account' => [ 'my-account' => 'Kontoen min', 'change-password' => 'Endre passord', diff --git a/modules/Admin/Language/nn-no/Notifications.php b/modules/Admin/Language/nn-no/Notifications.php new file mode 100644 index 00000000..0ff8568b --- /dev/null +++ b/modules/Admin/Language/nn-no/Notifications.php @@ -0,0 +1,19 @@ + 'Varslingar', + 'reply' => '{actor_username} svara på innlegget ditt', + 'favourite' => '{actor_username} merkte innlegget ditt som favoritt', + 'reblog' => '{actor_username} delte innlegget ditt', + 'follow' => '{actor_username} fylgde deg', + 'no_notifications' => 'Ingen varslingar', + 'mark_all_as_read' => 'Merk alle som lesne', +]; diff --git a/modules/Admin/Language/nn-NO/Page.php b/modules/Admin/Language/nn-no/Page.php similarity index 100% rename from modules/Admin/Language/nn-NO/Page.php rename to modules/Admin/Language/nn-no/Page.php diff --git a/modules/Admin/Language/nn-NO/Pager.php b/modules/Admin/Language/nn-no/Pager.php similarity index 100% rename from modules/Admin/Language/nn-NO/Pager.php rename to modules/Admin/Language/nn-no/Pager.php diff --git a/modules/Admin/Language/nn-NO/Person.php b/modules/Admin/Language/nn-no/Person.php similarity index 96% rename from modules/Admin/Language/nn-NO/Person.php rename to modules/Admin/Language/nn-no/Person.php index 3b12626f..33578a54 100644 --- a/modules/Admin/Language/nn-NO/Person.php +++ b/modules/Admin/Language/nn-no/Person.php @@ -24,7 +24,7 @@ return [ 'form' => [ 'avatar' => 'Profilbilete', 'avatar_size_hint' => - 'Avatar must be squared and at least 400px wide and tall.', + 'Profilbiletet må vera kvadratisk og minst 400pkt breitt og høgt.', 'full_name' => 'Fullt namn', 'full_name_hint' => 'Dette er det fulle namnet eller aliaset til personen.', 'unique_name' => 'Unikt namn', diff --git a/modules/Admin/Language/nn-NO/Platforms.php b/modules/Admin/Language/nn-no/Platforms.php similarity index 68% rename from modules/Admin/Language/nn-NO/Platforms.php rename to modules/Admin/Language/nn-no/Platforms.php index 3cf5f254..5d1e2a75 100644 --- a/modules/Admin/Language/nn-NO/Platforms.php +++ b/modules/Admin/Language/nn-no/Platforms.php @@ -9,9 +9,22 @@ declare(strict_types=1); */ return [ - 'title' => 'Plattformer', + 'title' => [ + 'podcasting' => 'Podkastplattformer', + 'social' => 'Sosiale nettverk', + 'funding' => 'Donasjonslenker', + ], + 'website' => 'Nettside', 'home_url' => 'Gå til {platformName}-nettstaden', + 'register' => 'Registrer deg', 'submit_url' => 'Send podkasten din til {platformName}', + 'your_link' => 'Lenka di', + 'your_id' => [ + 'podcasting' => 'ID-en din', + 'social' => 'ID-en din', + 'funding' => 'Lokkeordet ditt', + ], + 'your_cta' => 'Lokkeordet ditt', 'visible' => 'Vis på heimesida til podkasten?', 'on_embed' => 'Vis i den innbyggbare spelaren?', 'remove' => 'Fjern {platformName}', diff --git a/modules/Admin/Language/nn-NO/Podcast.php b/modules/Admin/Language/nn-no/Podcast.php similarity index 63% rename from modules/Admin/Language/nn-NO/Podcast.php rename to modules/Admin/Language/nn-no/Podcast.php index f18cefc2..18ef937e 100644 --- a/modules/Admin/Language/nn-NO/Podcast.php +++ b/modules/Admin/Language/nn-no/Podcast.php @@ -13,54 +13,57 @@ return [ 'no_podcast' => 'Fann ingen podkast!', 'create' => 'Lag ein podcast', 'import' => 'Importer ein podkast', + 'all_imports' => 'Podkast-importar', 'new_episode' => 'Ny episode', 'view' => 'Sjå podkasten', 'edit' => 'Rediger podkasten', - 'publish' => 'Publish podcast', - 'publish_edit' => 'Edit publication', + 'publish' => 'Legg ut podkasten', + 'publish_edit' => 'Rediger publiseringa', 'delete' => 'Slett podkasten', 'see_episodes' => 'Sjå episodane', 'see_contributors' => 'Sjå bidragsytarane', + 'monetization_other' => 'Andre måtar å tena pengar på', 'go_to_page' => 'Gå til side', 'latest_episodes' => 'Dei nyaste episodane', 'see_all_episodes' => 'Sjå alle episodane', - 'draft' => 'Draft', + 'draft' => 'Kladd', 'messages' => [ - 'createSuccess' => 'Podcast successfully created!', + 'createSuccess' => 'Podkasten er oppretta!', 'editSuccess' => 'Podkasten er oppdatert!', 'importSuccess' => 'Podkasten er importert!', - 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', - 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, - cover {cover} - banner {banner} + 'deleteSuccess' => 'Podkasten @{podcast_handle} vart sletta.', + 'deletePodcastMediaError' => 'Greidde ikkje sletta {type, select, + cover {omslaget} + banner {banneret} other {media} - }.', - 'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select, - transcript {transcript} - chapters {chapters} - image {cover} - audio {audio} + } til podkasten.', + 'deleteEpisodeMediaError' => 'Greidde ikkje sletta {type, select, + transcript {transkripsjonen} + chapters {kapittel} + image {omslag} + audio {lyd} other {media} - }.', - 'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.', - 'podcastFeedUpdateSuccess' => 'Successful update: {number_of_new_episodes, plural, - one {# episode was} - other {# episodes were} - } added to the podcast!', - 'podcastFeedUpToDate' => 'Podcast is already up to date.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', - 'publishError' => 'This podcast is either already published or scheduled for publication.', - 'publishEditError' => 'This podcast is not scheduled for publication.', - 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', - 'scheduleDateError' => 'Schedule date must be set!', + } frå {episode_slug}.', + 'deletePodcastMediaFolderError' => 'Greidde ikkje sletta mediemappa {folder_path} for podkasten. Du kan sletta mappa manuelt.', + 'podcastFeedUpdateSuccess' => 'Vellukka oppdatering: {number_of_new_episodes, plural, + one {# episode vart lagt} + other {# episodar vart lagde} + } til podkasten!', + 'podcastFeedUpToDate' => 'Podkasten er allereie oppdatert.', + 'publishError' => 'Denne podkasten er allereie lagt ut eller planlagt for offentleggjering.', + 'publishEditError' => 'Denne podkasten er ikkje planlagt publisert.', + 'publishCancelSuccess' => 'Du har avbrote å leggja ut podkasten.', + 'scheduleDateError' => 'Du må velja publiseringsdato.', ], 'form' => [ 'identity_section_title' => 'Podkastidentitet', 'identity_section_subtitle' => 'Desse felta gjer at du blir lagt merke til.', + 'fediverse_section_title' => 'Allheim-identitet', + 'cover' => 'Podkastomslag', - 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'cover_size_hint' => 'Omslaget må vera kvadratisk, og minst 1400pkt breitt og høgt.', 'banner' => 'Podkastbanner', - 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', + 'banner_size_hint' => 'Banneret må ha 3:1-forhold og vera minst 1500pkt breitt.', 'banner_delete' => 'Slett podkastbanneret', 'title' => 'Tittel', 'handle' => 'Handtak', @@ -71,7 +74,17 @@ return [ 'episodic' => 'Med episodar', 'episodic_hint' => 'Viss det er meininga at episodane skal kunna lyttast til uansett rekkjefylgje. Dei nyaste episodane blir presenterte fyrst.', 'serial' => 'I serie', - 'serial_hint' => 'Viss det er meininga at episodane skal koma i ei bestemt rekkjefylgje. Dei eldste episodane blir presenterte fyrst.', + 'serial_hint' => 'Om det er meininga at episodane skal koma i ei bestemt rekkjefylgje. Episodane med lågast nummer blir presenterte fyrst.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium slik det blir vist av podcast:medium-merkelappen i RSS. Viss du endrar dette, kan det påverka korleis avspelarar viser straumen din.', + 'podcast' => 'Podkast', + 'podcast_hint' => 'Skildrar ein straum for eit podkast-show.', + 'music' => 'Musikk', + 'music_hint' => 'Ein straum med musikk organisert i eit "album" der kvart element er ein song på albumet.', + 'audiobook' => 'Lydbok', + 'audiobook_hint' => 'Spesifikke typar lyd med eit element per straum, eller der elementa er kapittel i boka.', ], 'description' => 'Skildring', 'classification_section_title' => 'Klassifisering', @@ -96,6 +109,8 @@ return [ 'owner_email' => 'Epost til eigaren', 'owner_email_hint' => 'Blir brukt av dei fleste plattformer til å stadfesta eigarskapen til podkasten. Synleg i den offentlege RSS-straumen.', + 'is_owner_email_removed_from_feed' => 'Fjernar eposten til eigaren frå den offentlege RSS-straumen', + 'is_owner_email_removed_from_feed_hint' => 'Det kan henda du mellombels må visa eposten din slik at ei katalogtenest kan stadfesta at du eig podkasten.', 'publisher' => 'Utgjevar', 'publisher_hint' => 'Gruppa som er ansvarleg for serien. Det er vanlegvis morselskapet eller nettverket til ein podkast. Dette feltet er stundom merka med «forfattar».', @@ -108,8 +123,13 @@ return [ 'monetization_section_subtitle' => 'Ten pengar med hjelp frå publikummet ditt.', 'premium' => 'Premium', - 'premium_by_default' => 'Episodes must be set as premium by default', - 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'premium_by_default' => 'Episodane må ha premium som standardval', + 'premium_by_default_hint' => 'Podkastepisodane vil få premium som standardmerking. Du kan likevel ha nokre episodar, trailerar eller bonusar som offentlege.', + 'op3' => 'Open Podcast Prefix-prosjekt (OP3)', + 'op3_link' => 'Gå til OP3-styringspanelet ditt (ekstern lenke)', + 'op3_hint' => 'Verdiset analysedataa dine med OP3, som er ein tredjeparts analyseteneste med open kjeldekode. Del, stadfest og samanlikne analysedataa dine med det opne podkast-økosystemet.', + 'op3_enable' => 'Bruk OP3-analysetenesta', + 'op3_enable_hint' => 'Av tryggleiksgrunnar deler me ikkje analysedata for premium-episodar med OP3.', 'payment_pointer' => 'Betalingspunkt for nettkommersialisering', 'payment_pointer_hint' => 'Det er her du vil få inn pengar frå nettkommersialiseringa', @@ -118,11 +138,12 @@ return [ 'Viss du treng RSS-merkelappar som Castopod ikkje handterer, kan du skriva dei inn her.', 'custom_rss' => 'Eigne RSS-merkelappar for podkasten', 'custom_rss_hint' => 'Dette blir sett inn i ❬channel❭-elementet.', + 'verify_txt' => 'Stadfesting av eigarskap TXT', + 'verify_txt_hint' => 'I staden for å bruka epost, kan nokre tredjepartstenester stadfesta at du eig podkasten ved å be deg setja inn ein godkjenningstekst i straumen din.', + 'verify_txt_helper' => 'Denne teksten blir sett inn i ein -knagg.', 'new_feed_url' => 'Ny straum-URL', 'new_feed_url_hint' => 'Bruk dette feltet når du flyttar til eit anna domene eller vertsplattform. Standardvalet for verdien er den noverande RSS-adresse viss podkasten er importert.', - 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', + 'old_feed_url' => 'Gamal straum-URL', 'partnership' => 'Partnarskap', 'partner_id' => 'ID', 'partner_link_url' => 'Lenke-URL', @@ -130,10 +151,9 @@ return [ 'partner_id_hint' => 'Din eigen partnar-ID', 'partner_link_url_hint' => 'Lenkeadressa til den generelle partnaren', 'partner_image_url_hint' => 'Biletadressa til den generelle partnaren', - 'status_section_title' => 'Status', - 'block' => 'Podcast should be hidden from public catalogues', + 'block' => 'Podkasten skal gøymast frå offentlege katalogar', 'block_hint' => - 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'Vis- eller gøym- status for podkasten: Dersom du skrur på dette, hindrar det heile podkasten frå å syna i Apple podcasts, Google podcasts og andre tredjeparts-appar som hentar podkastar frå desse (men ingen garanti)', 'complete' => 'Podkasten vil ikkje få fleire episodar', 'lock' => 'Hindre at podkasten blir kopiert', 'lock_hint' => @@ -255,36 +275,36 @@ return [ 'tv_reviews' => 'TV-meldingar', ], 'publish_form' => [ - 'back_to_podcast_dashboard' => 'Back to podcast dashboard', - 'post' => 'Your announcement post', + 'back_to_podcast_dashboard' => 'Tilbake til podkast-styringspanelet', + 'post' => 'Kunngjeringsinnlegget ditt', 'post_hint' => - "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", - 'message_placeholder' => 'Write your message…', - 'submit' => 'Publish', - 'publication_date' => 'Publication date', + "Skriv ei melding for å kunngjera at du legg ut podkasten. Dette vil stå på hiemesida til podkasten din.", + 'message_placeholder' => 'Skriv kunngjeringa di…', + 'submit' => 'Legg ut', + 'publication_date' => 'Publiseringsdato', 'publication_method' => [ - 'now' => 'Now', - 'schedule' => 'Schedule', + 'now' => 'No', + 'schedule' => 'Tidsplan', ], - 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date' => 'Planlagd publiseringsdato', 'scheduled_publication_date_hint' => - 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', - 'submit_edit' => 'Edit publication', - 'cancel_publication' => 'Cancel publication', - 'message_warning' => 'You did not write a message for your announcement post!', - 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', - 'message_warning_submit' => 'Publish anyway', + 'Du kan planleggja å offengleggjera podkasten seinare ved å skriva inn eit publiseringstidspunkt. Feltet må vera i formatet ÅÅÅÅ-MM-DD HH:mm', + 'submit_edit' => 'Rediger publiseringa', + 'cancel_publication' => 'Avbryt publisering', + 'message_warning' => 'Du skreiv inga melding til kunngjeringsinnlegget ditt!', + 'message_warning_hint' => 'Viss du skriv ei melding, kan det gje meir sosialt engasjement og syta for at podkasten din blir meir synleg.', + 'message_warning_submit' => 'Legg ut likevel', ], 'publication_status_banner' => [ - 'draft_mode' => 'draft mode', - 'not_published' => 'This podcast is not yet published.', - 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + 'draft_mode' => 'kladdemodus', + 'not_published' => 'Denne podkasten er ikkje lagt ut enno.', + 'scheduled' => 'Denne podkasten er planlagt for utgjeving {publication_date}.', ], 'delete_form' => [ 'disclaimer' => - "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", - 'understand' => 'I understand, I want the podcast to be permanently deleted', - 'submit' => 'Delete', + "Viss du slettar podkasten, vil det sletta alle episodar, mediafiler, innlegg og analyse som høyrer til. Du kan ikkje angra dette, og vil ikkje få det tilbake etterpå.", + 'understand' => 'Eg forstår, og vil sletta podkasten for alltid', + 'submit' => 'Slett', ], 'by' => 'Av {publisher}', 'season' => 'Sesong {seasonNumber}', @@ -294,12 +314,12 @@ return [ 'no_episode' => 'Fann ingen episode!', 'follow' => 'Fylg', 'followers' => '{numberOfFollowers, plural, - one {# follower} - other {# followers} + one {# fylgjar} + other {# fylgjarar} }', 'posts' => '{numberOfPosts, plural, - one {# post} - other {# posts} + one {# innlegg} + other {# innlegg} }', 'activity' => 'Aktivitet', 'episodes' => 'Episodar', diff --git a/modules/Admin/Language/nn-NO/PodcastNavigation.php b/modules/Admin/Language/nn-no/PodcastNavigation.php similarity index 71% rename from modules/Admin/Language/nn-NO/PodcastNavigation.php rename to modules/Admin/Language/nn-no/PodcastNavigation.php index d62080ae..301692bd 100644 --- a/modules/Admin/Language/nn-NO/PodcastNavigation.php +++ b/modules/Admin/Language/nn-no/PodcastNavigation.php @@ -10,10 +10,13 @@ declare(strict_types=1); return [ 'go_to_page' => 'Gå til podkastsida', + 'rss_feed' => 'RSS-straum', 'dashboard' => 'Podkast-styringspanel', 'podcast-view' => 'Heim', 'podcast-edit' => 'Rediger podkasten', 'podcast-persons-manage' => 'Handter personar', + 'podcast-imports' => 'Podkast-importar', + 'podcast-imports-sync' => 'Synkroniser straumar', 'episodes' => 'Episodar', 'episode-list' => 'Alle episodane', 'episode-create' => 'Ny episode', @@ -25,14 +28,15 @@ return [ 'podcast-analytics-players' => 'Spelarar', 'podcast-analytics-listening-time' => 'Lyttetid', 'podcast-analytics-time-periods' => 'Tidsperiodar', - 'premium' => 'Premium', - 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', + 'monetization' => 'Kommersialisering', + 'subscription-list' => 'Alle abonnement', + 'subscription-create' => 'Legg til abonnement', 'contributors' => 'Bidragsytarar', 'contributor-list' => 'Alle bidragsytarane', 'contributor-add' => 'Legg til bidragsytar', - 'platforms' => 'Eksterne plattformer', - 'platforms-podcasting' => 'Podkasting', + 'broadcast' => 'Kringkast', + 'platforms-podcasting' => 'Podkastingsappar', 'platforms-social' => 'Sosiale nettverk', - 'platforms-funding' => 'Finansiering', + 'platforms-funding' => 'Donasjonslenker', + 'podcast-monetization-other' => 'Anna', ]; diff --git a/modules/Admin/Language/nn-NO/Settings.php b/modules/Admin/Language/nn-no/Settings.php similarity index 86% rename from modules/Admin/Language/nn-NO/Settings.php rename to modules/Admin/Language/nn-no/Settings.php index 6d9f6641..d05ccfc9 100644 --- a/modules/Admin/Language/nn-NO/Settings.php +++ b/modules/Admin/Language/nn-no/Settings.php @@ -15,7 +15,7 @@ return [ 'site_icon' => 'Sideikon', 'site_icon_delete' => 'Slett sideikonet', 'site_icon_hint' => 'Nettstadikon er det du ser i fanene på nettlesaren, bokmerkelina og når du legg til ein nettstad som snarveg på mobile einingar.', - 'site_icon_helper' => 'Icon must be squared and at least 512px wide and tall.', + 'site_icon_helper' => 'Ikonet må vera kvadratisk og minst 512pkt breitt og høgt.', 'site_name' => 'Nettstadnamn', 'site_description' => 'Skildring av nettstaden', 'submit' => 'Lagre', @@ -35,8 +35,8 @@ return [ 'reset_counts_helper' => 'Dette nullstiller alle datateljarar (tal på fylgjarar, innlegg, kommentarar…).', 'rewrite_media' => 'Overskriv metadata for medium', 'rewrite_media_helper' => 'Dette vil sletta alle overflødige mediafiler og laga dei på nytt (bilete, lydfiler, transkriberingar, kapittel, …)', - 'rename_episodes_files' => 'Rename episode audio files', - 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', + 'rename_episodes_files' => 'Gje episode-lydfilene nytt namn', + 'rename_episodes_files_hint' => 'Dette alternativet gjev alle episode-lydfilene tilfeldige nye namn. Bruk dette viss lenka til dei private episodane vart leken, for då blir ho i praksis gøymt att.', 'clear_cache' => 'Slett bufferinnhald', 'clear_cache_helper' => 'Dette tømmer redis-mellomlageret eller skrivbare/mellomlagra filer.', 'run' => 'Gjer reinhald', diff --git a/modules/Admin/Language/nn-NO/Soundbite.php b/modules/Admin/Language/nn-no/Soundbite.php similarity index 100% rename from modules/Admin/Language/nn-NO/Soundbite.php rename to modules/Admin/Language/nn-no/Soundbite.php diff --git a/modules/Admin/Language/nn-NO/Validation.php b/modules/Admin/Language/nn-no/Validation.php similarity index 79% rename from modules/Admin/Language/nn-NO/Validation.php rename to modules/Admin/Language/nn-no/Validation.php index ed99da37..21a34148 100644 --- a/modules/Admin/Language/nn-NO/Validation.php +++ b/modules/Admin/Language/nn-no/Validation.php @@ -13,6 +13,5 @@ return [ '{field} er anten ikkje eit bilete, eller er ikkje breitt og høgt nok.', 'is_image_ratio' => '{field} er anten ikkje eit bilete, eller har feil forhold mellom høgd og breidd.', - 'validate_url' => - '{field}-feltet må vera ei gyldig nettadresse (td. https://eksempel.no/).', + 'is_json' => '{field} inneheld ugyldig JSON.', ]; diff --git a/modules/Admin/Language/nn-NO/VideoClip.php b/modules/Admin/Language/nn-no/VideoClip.php similarity index 100% rename from modules/Admin/Language/nn-NO/VideoClip.php rename to modules/Admin/Language/nn-no/VideoClip.php diff --git a/modules/Admin/Language/oc/AboutCastopod.php b/modules/Admin/Language/oc/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/oc/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/oc/Breadcrumb.php b/modules/Admin/Language/oc/Breadcrumb.php index f3269bfa..408c9f9f 100644 --- a/modules/Admin/Language/oc/Breadcrumb.php +++ b/modules/Admin/Language/oc/Breadcrumb.php @@ -19,6 +19,7 @@ return [ 'pages' => 'pages', 'settings' => 'settings', 'theme' => 'theme', + 'about' => 'about', 'add' => 'add', 'new' => 'new', 'edit' => 'edit', @@ -28,15 +29,19 @@ return [ 'publish-date-edit' => 'edit publication date', 'unpublish' => 'unpublish', 'delete' => 'delete', + 'remove' => 'remove', 'fediverse' => 'fediverse', - 'block-lists' => 'block lists', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', 'users' => 'users', 'my-account' => 'my account', 'change-password' => 'change password', - 'import' => 'feed import', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', 'platforms' => 'platforms', 'social' => 'social networks', 'funding' => 'funding', + 'monetization-other' => 'other monetization', 'analytics' => 'analytics', 'locations' => 'locations', 'webpages' => 'web pages', diff --git a/modules/Admin/Language/oc/Charts.php b/modules/Admin/Language/oc/Charts.php index 4b33530e..6ede2510 100644 --- a/modules/Admin/Language/oc/Charts.php +++ b/modules/Admin/Language/oc/Charts.php @@ -37,4 +37,5 @@ return [ 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', 'total_storage_by_month' => 'Monthly storage (in MB)', 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', ]; diff --git a/modules/Admin/Language/oc/Common.php b/modules/Admin/Language/oc/Common.php index 596c8bcd..74addcf2 100644 --- a/modules/Admin/Language/oc/Common.php +++ b/modules/Admin/Language/oc/Common.php @@ -40,6 +40,7 @@ return [ ], 'upload_file' => 'Upload a file', 'remote_url' => 'Remote URL', + 'save' => 'Save', ], 'play_episode_button' => [ 'play' => 'Play', diff --git a/modules/Admin/Language/oc/Contributor.php b/modules/Admin/Language/oc/Contributor.php deleted file mode 100644 index d0f3b93d..00000000 --- a/modules/Admin/Language/oc/Contributor.php +++ /dev/null @@ -1,41 +0,0 @@ - 'Podcast contributors', - 'view' => "{username}'s contribution to {podcastTitle}", - 'add' => 'Add contributor', - 'add_contributor' => 'Add a contributor for {0}', - 'edit_role' => 'Update role for {0}', - 'edit' => 'Edit', - 'remove' => 'Remove', - 'list' => [ - 'username' => 'Username', - 'role' => 'Role', - ], - 'form' => [ - 'user' => 'User', - 'user_placeholder' => 'Select a user…', - 'role' => 'Role', - 'role_placeholder' => 'Select its role…', - 'submit_add' => 'Add contributor', - 'submit_edit' => 'Update role', - ], - 'roles' => [ - 'podcast_admin' => 'Podcast admin', - ], - 'messages' => [ - 'removeOwnerError' => "You can't remove the podcast owner!", - 'removeSuccess' => - 'You have successfully removed {username} from {podcastTitle}', - 'alreadyAddedError' => - "The contributor you're trying to add has already been added!", - ], -]; diff --git a/modules/Admin/Language/oc/Episode.php b/modules/Admin/Language/oc/Episode.php index 91313a7c..4fa846e3 100644 --- a/modules/Admin/Language/oc/Episode.php +++ b/modules/Admin/Language/oc/Episode.php @@ -22,6 +22,7 @@ return [ 'all_podcast_episodes' => 'All podcast episodes', 'back_to_podcast' => 'Go back to podcast', 'edit' => 'Edit', + 'preview' => 'Preview', 'publish' => 'Publish', 'publish_edit' => 'Edit publication', 'publish_date_edit' => 'Edit publication date', @@ -55,6 +56,7 @@ return [ }', 'episode' => 'Episode', 'visibility' => 'Visibility', + 'downloads' => 'Downloads', 'comments' => 'Comments', 'actions' => 'Actions', ], @@ -85,7 +87,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ @@ -137,9 +139,9 @@ return [ 'location_name' => 'Location name or address', 'location_name_hint' => 'This can be a real or fictional location', 'transcript' => 'Transcript (subtitles / closed captions)', - 'transcript_hint' => 'Only .srt are allowed.', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', 'transcript_download' => 'Download transcript', - 'transcript_file' => 'Transcript file (.srt)', + 'transcript_file' => 'Transcript file (.srt or .vtt)', 'transcript_remote_url' => 'Remote url for transcript', 'transcript_file_delete' => 'Delete transcript file', 'chapters' => 'Chapters', @@ -210,4 +212,14 @@ return [ 'light' => 'Light', 'light-transparent' => 'Light transparent', ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], ]; diff --git a/modules/Admin/Language/oc/Home.php b/modules/Admin/Language/oc/Home.php index 3ff4c04d..2f0f3570 100644 --- a/modules/Admin/Language/oc/Home.php +++ b/modules/Admin/Language/oc/Home.php @@ -9,6 +9,6 @@ declare(strict_types=1); */ return [ - 'all_podcasts' => 'All podcasts', - 'no_podcast' => 'No podcast found', + 'all_podcasts' => 'Totes los podcasts', + 'no_podcast' => 'Cap de podcast pas trobat', ]; diff --git a/modules/Admin/Language/oc/Install.php b/modules/Admin/Language/oc/Install.php index 36e373a2..5a90a8ce 100644 --- a/modules/Admin/Language/oc/Install.php +++ b/modules/Admin/Language/oc/Install.php @@ -9,7 +9,7 @@ declare(strict_types=1); */ return [ - 'manual_config' => 'Manual configuration', + 'manual_config' => 'Configuracion manuala', 'manual_config_subtitle' => 'Create a `.env` file with your settings and refresh the page to continue installation.', 'form' => [ @@ -39,12 +39,12 @@ return [ 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', 'cache_handler' => 'Cache handler', 'cacheHandlerOptions' => [ - 'file' => 'File', + 'file' => 'Fichièr', 'redis' => 'Redis', 'predis' => 'Predis', ], - 'next' => 'Next', - 'submit' => 'Finish install', + 'next' => 'Seguent', + 'submit' => 'Acabar l’installacion', 'create_superadmin' => 'Create your superadmin account', 'email' => 'Email', 'username' => 'Username', diff --git a/modules/Admin/Language/oc/Navigation.php b/modules/Admin/Language/oc/Navigation.php index 68d4609d..f3ffb129 100644 --- a/modules/Admin/Language/oc/Navigation.php +++ b/modules/Admin/Language/oc/Navigation.php @@ -12,12 +12,14 @@ return [ 'toggle_sidebar' => 'Toggle sidebar', 'go_to_website' => 'Go to website', 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', 'dashboard' => 'Dashboard', 'admin' => 'Home', 'podcasts' => 'Podcasts', 'podcast-list' => 'All podcasts', 'podcast-create' => 'New podcast', - 'podcast-import' => 'Import a podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', 'persons' => 'Persons', 'person-list' => 'All persons', 'person-create' => 'New person', @@ -33,6 +35,7 @@ return [ 'settings' => 'Settings', 'settings-general' => 'General', 'settings-theme' => 'Theme', + 'admin-about' => 'About', 'account' => [ 'my-account' => 'My account', 'change-password' => 'Change password', diff --git a/modules/Admin/Language/oc/Platforms.php b/modules/Admin/Language/oc/Platforms.php index ab17d599..e161181c 100644 --- a/modules/Admin/Language/oc/Platforms.php +++ b/modules/Admin/Language/oc/Platforms.php @@ -9,9 +9,22 @@ declare(strict_types=1); */ return [ - 'title' => 'Platforms', + 'title' => [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', 'visible' => 'Display in podcast homepage?', 'on_embed' => 'Display on embeddable player?', 'remove' => 'Remove {platformName}', diff --git a/modules/Admin/Language/oc/Podcast.php b/modules/Admin/Language/oc/Podcast.php index 426b763b..ff0daebc 100644 --- a/modules/Admin/Language/oc/Podcast.php +++ b/modules/Admin/Language/oc/Podcast.php @@ -13,6 +13,7 @@ return [ 'no_podcast' => 'No podcast found!', 'create' => 'Create podcast', 'import' => 'Import podcast', + 'all_imports' => 'Podcast imports', 'new_episode' => 'New Episode', 'view' => 'View podcast', 'edit' => 'Edit podcast', @@ -21,6 +22,7 @@ return [ 'delete' => 'Delete podcast', 'see_episodes' => 'See episodes', 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', 'go_to_page' => 'Go to page', 'latest_episodes' => 'Latest episodes', 'see_all_episodes' => 'See all episodes', @@ -48,7 +50,6 @@ return [ other {# episodes were} } added to the podcast!', 'podcastFeedUpToDate' => 'Podcast is already up to date.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', 'publishError' => 'This podcast is either already published or scheduled for publication.', 'publishEditError' => 'This podcast is not scheduled for publication.', 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', @@ -57,6 +58,8 @@ return [ 'form' => [ 'identity_section_title' => 'Podcast identity', 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + 'cover' => 'Podcast cover', 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', 'banner' => 'Podcast banner', @@ -71,7 +74,17 @@ return [ 'episodic' => 'Episodic', 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', 'serial' => 'Serial', - 'serial_hint' => 'If episodes are intended to be consumed in sequential order. The oldest episodes will be presented first.', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', ], 'description' => 'Description', 'classification_section_title' => 'Classification', @@ -96,6 +109,8 @@ return [ 'owner_email' => 'Owner email', 'owner_email_hint' => 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', 'publisher' => 'Publisher', 'publisher_hint' => 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', @@ -110,6 +125,11 @@ return [ 'premium' => 'Premium', 'premium_by_default' => 'Episodes must be set as premium by default', 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', 'payment_pointer' => 'Payment Pointer for Web Monetization', 'payment_pointer_hint' => 'This is your where you will receive money thanks to Web Monetization', @@ -118,11 +138,12 @@ return [ 'If you need RSS tags that Castopod does not handle, set them here.', 'custom_rss' => 'Custom RSS tags for the podcast', 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', 'new_feed_url' => 'New feed URL', 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', 'partnership' => 'Partnership', 'partner_id' => 'ID', 'partner_link_url' => 'Link URL', @@ -130,7 +151,6 @@ return [ 'partner_id_hint' => 'Your own partner ID', 'partner_link_url_hint' => 'The generic partner link address', 'partner_image_url_hint' => 'The generic partner image address', - 'status_section_title' => 'Status', 'block' => 'Podcast should be hidden from public catalogues', 'block_hint' => 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', diff --git a/modules/Admin/Language/oc/PodcastImport.php b/modules/Admin/Language/oc/PodcastImport.php deleted file mode 100644 index 7c3ef67d..00000000 --- a/modules/Admin/Language/oc/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'The podcast to import', - 'old_podcast_section_subtitle' => - 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', - 'imported_feed_url' => 'Feed URL', - 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', - 'new_podcast_section_title' => 'The new podcast', - 'advanced_params_section_title' => 'Advanced parameters', - 'advanced_params_section_subtitle' => - 'Keep the default values if you have no idea of what the fields are for.', - 'slug_field' => 'Field to be used to calculate episode slug', - 'description_field' => - 'Source field used for episode description / show notes', - 'force_renumber' => 'Force episodes renumbering', - 'force_renumber_hint' => - 'Use this if your podcast does not have episode numbers but wish to set them during import.', - 'season_number' => 'Season number', - 'season_number_hint' => - 'Use this if your podcast does not have a season number but wish to set one during import. Leave blank otherwise.', - 'max_episodes' => 'Maximum number of episodes to import', - 'max_episodes_hint' => 'Leave blank to import all episodes', - 'lock_import' => - 'This feed is protected. You cannot import it. If you are the owner, unprotect it on the origin platform.', - 'submit' => 'Import podcast', -]; diff --git a/modules/Admin/Language/oc/PodcastNavigation.php b/modules/Admin/Language/oc/PodcastNavigation.php index b4d7ddc0..bb777707 100644 --- a/modules/Admin/Language/oc/PodcastNavigation.php +++ b/modules/Admin/Language/oc/PodcastNavigation.php @@ -10,10 +10,13 @@ declare(strict_types=1); return [ 'go_to_page' => 'Go to podcast page', + 'rss_feed' => 'RSS feed', 'dashboard' => 'Podcast dashboard', 'podcast-view' => 'Home', 'podcast-edit' => 'Edit podcast', 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', 'episodes' => 'Episodes', 'episode-list' => 'All episodes', 'episode-create' => 'New episode', @@ -25,14 +28,15 @@ return [ 'podcast-analytics-players' => 'Players', 'podcast-analytics-listening-time' => 'Listening time', 'podcast-analytics-time-periods' => 'Time periods', - 'premium' => 'Premium', + 'monetization' => 'Monetization', 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', + 'subscription-create' => 'Add subscription', 'contributors' => 'Contributors', 'contributor-list' => 'All contributors', 'contributor-add' => 'Add contributor', - 'platforms' => 'External platforms', - 'platforms-podcasting' => 'Podcasting', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', 'platforms-social' => 'Social networks', - 'platforms-funding' => 'Funding', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', ]; diff --git a/modules/Admin/Language/oc/User.php b/modules/Admin/Language/oc/User.php deleted file mode 100644 index 585d6799..00000000 --- a/modules/Admin/Language/oc/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', - 'ban' => 'Ban', - 'unban' => 'Unban', - 'delete' => 'Delete', - 'create' => 'New user', - 'view' => "{username}'s info", - 'all_users' => 'All users', - 'list' => [ - 'user' => 'User', - 'roles' => 'Roles', - 'banned' => 'Banned?', - ], - 'form' => [ - 'email' => 'Email', - 'username' => 'Username', - 'password' => 'Password', - 'new_password' => 'New Password', - 'roles' => 'Roles', - 'permissions' => 'Permissions', - 'submit_create' => 'Create user', - 'submit_edit' => 'Save', - 'submit_password_change' => 'Change!', - ], - 'roles' => [ - 'superadmin' => 'Super admin', - ], - 'messages' => [ - 'createSuccess' => - 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => - "{username}'s roles have been successfully updated.", - 'forcePassResetSuccess' => - '{username} will be prompted with a password reset upon next visit.', - 'banSuccess' => '{username} has been banned.', - 'unbanSuccess' => '{username} has been unbanned.', - 'editOwnerError' => - '{username} is the instance owner, you cannot edit its roles.', - 'banSuperAdminError' => - '{username} is a superadmin, one does not simply ban a superadmin…', - 'deleteSuperAdminError' => - '{username} is a superadmin, one does not simply delete a superadmin…', - 'deleteSuccess' => '{username} has been deleted.', - ], -]; diff --git a/modules/Admin/Language/oc/Validation.php b/modules/Admin/Language/oc/Validation.php index 750b1968..f76c3163 100644 --- a/modules/Admin/Language/oc/Validation.php +++ b/modules/Admin/Language/oc/Validation.php @@ -13,6 +13,5 @@ return [ '{field} is either not an image, or it is not wide or tall enough.', 'is_image_ratio' => '{field} is either not an image or not of the right ratio.', - 'validate_url' => - 'The {field} field must be a valid URL (eg. https://example.com/).', + 'is_json' => '{field} contains invalid JSON.', ]; diff --git a/modules/Admin/Language/pl/AboutCastopod.php b/modules/Admin/Language/pl/AboutCastopod.php new file mode 100644 index 00000000..2a65d0d6 --- /dev/null +++ b/modules/Admin/Language/pl/AboutCastopod.php @@ -0,0 +1,22 @@ + 'O Castopod', + 'host_name' => 'Nazwa hosta', + 'version' => 'Wersja Castopod', + 'php_version' => 'Wersja PHP', + 'os' => 'System operacyjny', + 'languages' => 'Języki', + 'update_database' => 'Aktualizuj bazę danych', + 'messages' => [ + 'databaseUpdateSuccess' => 'Baza danych jest aktualna!', + ], +]; diff --git a/modules/Admin/Language/pl/Breadcrumb.php b/modules/Admin/Language/pl/Breadcrumb.php index d472ef10..34ba50a6 100644 --- a/modules/Admin/Language/pl/Breadcrumb.php +++ b/modules/Admin/Language/pl/Breadcrumb.php @@ -14,39 +14,44 @@ return [ ->gateway => 'Początek', 'podcasts' => 'podcasty', 'episodes' => 'odcinki', - 'subscriptions' => 'subscriptions', + 'subscriptions' => 'subskrypcja', 'contributors' => 'kontrybutorzy', 'pages' => 'strony', 'settings' => 'ustawienia', 'theme' => 'motyw', + 'about' => 'informacje', 'add' => 'dodaj', 'new' => 'nowy', 'edit' => 'edytuj', 'persons' => 'osoby', 'publish' => 'publikuj', 'publish-edit' => 'edytuj publikację', - 'publish-date-edit' => 'edit publication date', + 'publish-date-edit' => 'edytuj datę publikacji', 'unpublish' => 'cofnij publikację', 'delete' => 'usuń', - 'fediverse' => 'fediverse', - 'block-lists' => 'listy blokowanych', + 'remove' => 'usuń', + 'fediverse' => 'fediwersum', + 'blocked-actors' => 'zablokowani aktorzy', + 'blocked-domains' => 'zablokowane domeny', 'users' => 'użytkownicy', 'my-account' => 'moje konto', 'change-password' => 'zmień hasło', - 'import' => 'import kanału', + 'imports' => 'importy', + 'sync-feeds' => 'synchronizuj kanały', 'platforms' => 'platformy', 'social' => 'sieci społecznościowe', 'funding' => 'finansowanie', + 'monetization-other' => 'inna monetyzacja', 'analytics' => 'analityka', 'locations' => 'lokalizacje', 'webpages' => 'strony internetowe', 'unique-listeners' => 'unikalni słuchacze', 'players' => 'odtwarzacze', 'listening-time' => 'czas odsłuchu', - 'time-periods' => 'okresy czasu', + 'time-periods' => 'przedziały czasu', 'soundbites' => 'zajawki', 'video-clips' => 'klipy wideo', 'embed' => 'odtwarzacz do osadzenia', - 'notifications' => 'notifications', - 'suspend' => 'suspend', + 'notifications' => 'powiadomienia', + 'suspend' => 'wstrzymaj', ]; diff --git a/modules/Admin/Language/pl/Charts.php b/modules/Admin/Language/pl/Charts.php index 32eb4e20..ebd13d1d 100644 --- a/modules/Admin/Language/pl/Charts.php +++ b/modules/Admin/Language/pl/Charts.php @@ -15,8 +15,8 @@ return [ 'by_device_weekly' => 'Pobrania odcinków według urządzenia (dla minionego tygodnia)', 'by_os_weekly' => 'Pobrania odcinków według systemu operacyjnego (dla minionego tygodnia)', 'podcast_by_region' => 'Pobrania odcinków według regionu (dla minionego tygodnia)', - 'unique_daily_listeners' => 'Codzienni unikalni słuchacze', - 'unique_monthly_listeners' => 'Comiesięczni unikalni słuchacze', + 'unique_daily_listeners' => 'Unikalni słuchacze dziennie', + 'unique_monthly_listeners' => 'Unikalni słuchacze miesięcznie', 'by_browser' => 'Wykorzystanie stron internetowych według przeglądarki (dla minionego tygodnia)', 'podcast_by_day' => 'Dzienne pobrania odcinków', 'podcast_by_month' => 'Miesięczne pobrania odcinków', @@ -29,12 +29,13 @@ return [ 'by_domain_weekly' => 'Odwiedziny stron internetowych według źródła (dla minionego tygodnia)', 'by_domain_yearly' => 'Odwiedziny stron internetowych według źródła (dla minionego roku)', 'by_entry_page' => 'Odwiedziny stron internetowych według landing page (dla minionego tygodnia)', - 'podcast_bots' => 'Boty (robaki)', + 'podcast_bots' => 'Boty (pająki)', 'daily_listening_time' => 'Dzienny łączny czas słuchania', 'monthly_listening_time' => 'Miesięczny łączny czas słuchania', 'by_weekday' => 'Według dnia tygodnia (dla minionych 60 dni)', 'by_hour' => 'Według pory dnia (dla minionych 60 dni)', 'podcast_by_bandwidth' => 'Dzienna przepustowość (w MB)', - 'total_storage_by_month' => 'Monthly storage (in MB)', - 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_storage_by_month' => 'Miesięczne przechowywanie (w MB)', + 'total_bandwidth_by_month' => 'Miesięczne zużycie transferu (w MB)', + 'total_bandwidth_by_month_limit' => 'Ograniczono do {totalBandwidth} miesięcznie', ]; diff --git a/modules/Admin/Language/pl/Common.php b/modules/Admin/Language/pl/Common.php index 183399ee..5d3fba6b 100644 --- a/modules/Admin/Language/pl/Common.php +++ b/modules/Admin/Language/pl/Common.php @@ -40,12 +40,13 @@ return [ ], 'upload_file' => 'Prześlij plik', 'remote_url' => 'Zdalny URL', + 'save' => 'Zapisz', ], 'play_episode_button' => [ 'play' => 'Odtwarzaj', 'playing' => 'Odtwarzanie', ], 'size_limit' => 'Limit rozmiaru: {0}.', - 'choose_interact' => 'Choose how to interact', - 'view' => 'View', + 'choose_interact' => 'Wybierz sposób interakcji', + 'view' => 'Podgląd', ]; diff --git a/modules/Admin/Language/pl/Countries.php b/modules/Admin/Language/pl/Countries.php index 78a89f0b..53f104a8 100644 --- a/modules/Admin/Language/pl/Countries.php +++ b/modules/Admin/Language/pl/Countries.php @@ -11,7 +11,7 @@ declare(strict_types=1); */ return [ - 'AD' => 'Andorra', + 'AD' => 'Andora', 'AE' => 'Zjednoczone Emiraty Arabskie', 'AF' => 'Afganistan', 'AG' => 'Antigua i Barbuda', @@ -63,7 +63,7 @@ return [ 'CR' => 'Kostaryka', 'CU' => 'Kuba', 'CV' => 'Republika Zielonego Przylądka', - 'CW' => 'Curaçao', + 'CW' => 'Curacao', 'CX' => 'Wyspa Bożego Narodzenia', 'CY' => 'Cypr', 'CZ' => 'Czechy', @@ -121,7 +121,7 @@ return [ 'IR' => 'Iran', 'IS' => 'Islandia', 'IT' => 'Włochy', - 'JE' => 'Jersey', + 'JE' => 'Wyspa Jersey', 'JM' => 'Jamajka', 'JO' => 'Jordania', 'JP' => 'Japonia', @@ -146,12 +146,12 @@ return [ 'LT' => 'Litwa', 'LU' => 'Luksemburg', 'LV' => 'Łotwa', - 'LY' => 'Libya', + 'LY' => 'Libia', 'MA' => 'Maroko', 'MC' => 'Monako', 'MD' => 'Mołdawia', 'ME' => 'Czarnogóra', - 'MF' => 'Saint Martin (French part)', + 'MF' => 'Saint-Martin (Francja)', 'MG' => 'Madagaskar', 'MH' => 'Wyspy Marshalla', 'MK' => 'Macedonia Północna', @@ -224,7 +224,7 @@ return [ 'SV' => 'Salwador', 'SX' => 'Sint Maarten', 'SY' => 'Syria', - 'SZ' => 'Swaziland', + 'SZ' => 'Suazi', 'TC' => 'Turks i Caicos', 'TD' => 'Czad', 'TF' => 'Francuskie Terytoria Południowe i Antarktyczne', diff --git a/modules/Admin/Language/pl/Dashboard.php b/modules/Admin/Language/pl/Dashboard.php index 881073fd..26030e1f 100644 --- a/modules/Admin/Language/pl/Dashboard.php +++ b/modules/Admin/Language/pl/Dashboard.php @@ -9,20 +9,20 @@ declare(strict_types=1); */ return [ - 'home' => 'Admin dashboard', - 'welcome_message' => 'Welcome to the admin area!', + 'home' => 'Panel administratora', + 'welcome_message' => 'Witamy w panelu administracyjnym!', 'podcasts' => [ - 'title' => 'Podcasts', - 'not_found' => 'No published podcast', - 'last_published' => 'Last published on {lastPublicationDate}', + 'title' => 'Podcasty', + 'not_found' => 'Brak opublikowanych podcastów', + 'last_published' => 'Ostatnio opublikowane {lastPublicationDate}', ], 'episodes' => [ - 'title' => 'Episodes', - 'not_found' => 'No published episode', - 'last_published' => 'Last published on {lastPublicationDate}', + 'title' => 'Odcinki', + 'not_found' => 'Brak opublikowanych odcinków', + 'last_published' => 'Ostatnio opublikowane {lastPublicationDate}', ], 'storage' => [ - 'title' => 'Storage', - 'subtitle' => '{totalUploaded} out of {totalStorage}', + 'title' => 'Pamięć', + 'subtitle' => '{totalUploaded} z {totalStorage}', ], ]; diff --git a/modules/Admin/Language/pl/Episode.php b/modules/Admin/Language/pl/Episode.php index ee45c805..5c84ab2b 100644 --- a/modules/Admin/Language/pl/Episode.php +++ b/modules/Admin/Language/pl/Episode.php @@ -21,41 +21,44 @@ return [ other {# komentarzy} }', 'all_podcast_episodes' => 'Wszystkie odcinki podcastu', - 'back_to_podcast' => 'Wróć do podkastu', + 'back_to_podcast' => 'Wróć do podcastu', 'edit' => 'Edytuj', + 'preview' => 'Podgląd', 'publish' => 'Publikuj', 'publish_edit' => 'Edytuj publikację', - 'publish_date_edit' => 'Edit publication date', + 'publish_date_edit' => 'Edytuj datę publikacji', 'unpublish' => 'Cofnij publikację', 'publish_error' => 'Odcinek jest już opublikowany.', 'publish_edit_error' => 'Odcinek jest już opublikowany.', 'publish_cancel_error' => 'Odcinek jest już opublikowany.', - 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', - 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', - 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'publish_date_edit_error' => 'Odcinek nie został jeszcze opublikowany, nie możesz edytować daty jego publikacji.', + 'publish_date_edit_future_error' => 'Data publikacji odcinka może być ustawiona tylko na przeszłą datę! Jeśli chcesz ją ponownie zaplanować, należy najpierw cofnąć publikację.', + 'publish_date_edit_success' => 'Data publikacji odcinka została pomyślnie zaktualizowana!', 'unpublish_error' => 'Odcinek nie jest opublikowany.', 'delete' => 'Usuń', 'go_to_page' => 'Przejdź do strony', 'create' => 'Dodaj odcinek', 'publication_status' => [ 'published' => 'Opublikowany', - 'with_podcast' => 'Published', + 'with_podcast' => 'Opublikowano', 'scheduled' => 'Zaplanowany', 'not_published' => 'Nieopublikowany', ], - 'with_podcast_hint' => 'To be published at the same time as the podcast', + 'with_podcast_hint' => 'Opublikowany w tym samym czasie co podcast', 'list' => [ 'search' => [ - 'placeholder' => 'Search for an episode', - 'clear' => 'Clear search', - 'submit' => 'Search', + 'placeholder' => 'Szukaj odcinka', + 'clear' => 'Wyczyść wyszukiwanie', + 'submit' => 'Szukaj', ], 'number_of_episodes' => '{numberOfEpisodes, plural, - one {# episode} - other {# episodes} - }', + one {# osoba} + few {# osoby} + other {# osób} + }', 'episode' => 'Odcinek', 'visibility' => 'Widoczność', + 'downloads' => 'Pobrane', 'comments' => 'Komentarze', 'actions' => 'Działania', ], @@ -63,31 +66,31 @@ return [ 'createSuccess' => 'Odcinek został pomyślnie utworzony!', 'editSuccess' => 'Odcinek został pomyślnie zaktualizowany!', 'publishSuccess' => '{publication_status, select, - published {Episode successfully published!} - scheduled {Episode publication successfully scheduled!} - with_podcast {This episode will be published at the same time as the podcast.} - other {This episode is not published.} + published {Odcinek został pomyślnie opublikowany!} + scheduled {Publikacja odcinka pomyślnie zaplanowana!} + with_podcast {Ten odcinek zostanie opublikowany w tym samym czasie co podcast.} + other {Ten odcinek nie jest opublikowany.} }', - 'publishCancelSuccess' => 'Episode publication successfully cancelled!', - 'unpublishBeforeDeleteTip' => 'You must unpublish the episode before deleting it.', - 'scheduleDateError' => 'Schedule date must be set!', - 'deletePublishedEpisodeError' => 'Please unpublish the episode before deleting it.', - 'deleteSuccess' => 'Episode successfully deleted!', - 'deleteError' => 'Failed to delete episode {type, select, - transcript {transcript} - chapters {chapters} - image {cover} + 'publishCancelSuccess' => 'Publikacja odcinka pomyślnie anulowana!', + 'unpublishBeforeDeleteTip' => 'Musisz cofnąć publikację odcinka przed jego usunięciem.', + 'scheduleDateError' => 'Zaplanowana data musi być ustawiona!', + 'deletePublishedEpisodeError' => 'Musisz cofnąć publikację odcinka przed jego usunięciem.', + 'deleteSuccess' => 'Odcinek pomyślnie usunięty!', + 'deleteError' => 'Nie udało się usunąć {type, select, + transcript {transkrypcji} + chapters {rozdziału} + image {okładki} audio {audio} - other {media} - }.', - 'deleteFileError' => 'Failed to delete {type, select, - transcript {transcript} - chapters {chapters} - image {cover} + other {mediów} + } odcinka.', + 'deleteFileError' => 'Nie można było skasować pliku {type, select, + transcript {transkryptu} + chapters {rozdziałów} + image {okładki} audio {audio} - other {media} - } file {file_path}. You may manually remove it from your disk.', - 'sameSlugError' => 'An episode with the chosen slug already exists.', + other {medium} + } {file_key}. Możesz chcieć zrobić to ręcznie.', + 'sameSlugError' => 'Odcinek z wybranym slugiem już istnieje.', ], 'form' => [ 'file_size_error' => @@ -98,7 +101,7 @@ return [ 'cover' => 'Okładka odcinka', 'cover_hint' => 'Jeśli nie ustawisz okładki, zamiast niej zostanie użyta okładka podcastu.', - 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'cover_size_hint' => 'Okładka musi być kwadratowa o szerokości i wysokości co najmniej 1400 pikseli.', 'title' => 'Tytuł', 'title_hint' => 'Powinien zawierać jasną i zwięzłą nazwę odcinka. Nie podawaj tutaj numerów odcinków ani sezonów.', @@ -110,37 +113,37 @@ return [ 'full' => 'Pełny', 'full_hint' => 'Pełna zawartość (odcinek)', 'trailer' => 'Zwiastun', - 'trailer_hint' => 'Krótka, promocyjna treść przedstawiająca podgląd bieżącego programu', + 'trailer_hint' => 'Krótka, promocyjna treść przedstawiająca bieżący program', 'bonus' => 'Bonus', 'bonus_hint' => 'Dodatkowa treść do programu (np. informacje zza kulis lub wywiady z obsadą) albo treści promujące inne programy', ], 'premium_title' => 'Premium', - 'premium' => 'Episode must be accessible to premium subscribers only', + 'premium' => 'Odcinek dostępny wyłącznie dla subskrybentów premium', 'parental_advisory' => [ 'label' => 'Kontrola rodzicielska', 'hint' => 'Czy odcinek zawiera treści dla dorosłych?', - 'undefined' => 'nieokreślona', + 'undefined' => 'niezdefiniowano', 'clean' => 'Czysta', 'explicit' => 'Dla dorosłych', ], 'show_notes_section_title' => 'Notatki programu', 'show_notes_section_subtitle' => - 'Do 4000 znaków, bądź jasny i zwięźly. Notatki programu pomagają potencjalnym słuchaczom w znalezieniu odcinka.', + 'Do 4000 znaków, pisz krótko i zwięźle. Notatki programu pomagają potencjalnym słuchaczom w znalezieniu odcinka.', 'description' => 'Opis', 'description_footer' => 'Stopka opisu', 'description_footer_hint' => 'Ten tekst jest dodawany na końcu każdego opisu odcinka; jest to dobre miejsce do wpisania np. linków społecznościowych.', 'additional_files_section_title' => 'Dodatkowe pliki', 'additional_files_section_subtitle' => - 'These files may be used by other platforms to provide better experience to your audience. See the {podcastNamespaceLink} for more information.', + 'Pliki te mogą być używane przez inne platformy, aby zapewnić lepsze wrażenia odbiorcom. Więcej informacji znajdziesz w {podcastNamespaceLink}.', 'location_section_title' => 'Lokalizacja', 'location_section_subtitle' => 'O jakim miejscu jest ten odcinek?', 'location_name' => 'Nazwa lub adres lokalizacji', 'location_name_hint' => 'Może to być prawdziwa lub fikcyjna lokalizacja', 'transcript' => 'Transkrypcja (napisy / podpisy kodowane)', - 'transcript_hint' => 'Dozwolone tylko .srt.', + 'transcript_hint' => 'Wspierane są tylko .srt lub .vtt.', 'transcript_download' => 'Pobierz transkrypcję', - 'transcript_file' => 'Plik transkrypcji (.srt)', + 'transcript_file' => 'Plik transkrypcji (.srt lub .vtt)', 'transcript_remote_url' => 'Zdalny adres URL dla transkrypcji', 'transcript_file_delete' => 'Usuń plik transkrypcji', 'chapters' => 'Rozdziały', @@ -154,51 +157,51 @@ return [ 'Jeśli potrzebujesz tagów RSS, których Castopod nie obsługuje, ustaw je tutaj.', 'custom_rss' => 'Własne tagi RSS dla odcinka', 'custom_rss_hint' => 'Zostaną wstawione w tagu ❬item❭.', - 'block' => 'Episode should be hidden from public catalogues', + 'block' => 'Odcinek powinien być ukryty w publicznych katalogach', 'block_hint' => - 'The episode show or hide status: toggling this on prevents the episode from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'Pokazywanie lub ukrywanie odcinka: przełączanie tej funkcji zapobiega pojawieniu się odcinka w Apple Podcasts, Google Podcasts, a także aplikacjach innych firm, które pobierają z tych katalogów. (Niegwarantowane)', 'submit_create' => 'Stwórz odcinek', 'submit_edit' => 'Zapisz odcinek', ], 'publish_form' => [ - 'back_to_episode_dashboard' => 'Wróć do pulpitu odcinka', + 'back_to_episode_dashboard' => 'Wróć do panelu odcinka', 'post' => 'Twój wpis ogłoszeniowy', 'post_hint' => - "Napisz wiadomość, aby ogłosić publikację swojego odcinka. Wiadomość zostanie wyemitowana do wszystkich Twoich obserwujących w fediverse i pojawi się na stronie głównej Twojego podcastu.", + "Napisz wiadomość, aby ogłosić publikację swojego odcinka. Wiadomość zostanie wyemitowana do wszystkich Twoich obserwujących w fediwersum i pojawi się na stronie głównej Twojego podcastu.", 'message_placeholder' => 'Napisz swoją wiadomość…', 'publication_date' => 'Data publikacji', 'publication_method' => [ 'now' => 'Teraz', 'schedule' => 'Zaplanuj', - 'with_podcast' => 'Publish alongside podcast', + 'with_podcast' => 'Opublikuj razem z podcastem', ], 'scheduled_publication_date' => 'Planowana data publikacji', 'scheduled_publication_date_clear' => 'Wyczyść datę publikacji', 'scheduled_publication_date_hint' => - 'Możesz zaplanować wydanie odcinka ustawiając przyszłą datę publikacji. To pole musi być sformatowane jako YYYY-MM-DD HH:mm', + 'Możesz zaplanować wydanie odcinka, ustawiając przyszłą datę publikacji. To pole musi być sformatowane jako YYYY-MM-DD HH:mm', 'submit' => 'Opublikuj', 'submit_edit' => 'Edytuj publikację', 'cancel_publication' => 'Anuluj publikację', 'message_warning' => 'Nie napisałeś wiadomości do swojego wpisu ogłoszeniowego!', - 'message_warning_hint' => 'Posiadanie wiadomości zwiększa zaangażowanie społeczne, co skutkuje lepszą widocznością Twojego odcinka.', + 'message_warning_hint' => 'Napisanie wiadomości zwiększa zaangażowanie społeczne, co skutkuje lepszą widocznością Twojego odcinka.', 'message_warning_submit' => 'Opublikuj mimo to', ], 'publish_date_edit_form' => [ - 'new_publication_date' => 'New publication date', - 'new_publication_date_hint' => 'Must be set to a past date.', - 'submit' => 'Edit publication date', + 'new_publication_date' => 'Nowa data publikacji', + 'new_publication_date_hint' => 'Musi być ustawiona przeszła data.', + 'submit' => 'Edytuj datę publikacji', ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", + "Cofnięcie publikacji odcinka spowoduje usunięcie wszystkich powiązanych z nim wpisów i usunięcie go z kanału RSS podcastu.", 'understand' => 'Rozumiem, chcę cofnąć publikację odcinka', 'submit' => 'Cofnij publikację', ], 'delete_form' => [ 'disclaimer' => - "Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.", + "Usunięcie odcinka spowoduje usunięcie wszystkich plików multimedialnych, komentarzy, klipów wideo i powiązanych z nimi zajawek.", 'understand' => 'Rozumiem, chcę usunąć odcinek', - 'submit' => 'Delete', + 'submit' => 'Usuń', ], 'embed' => [ 'title' => 'Odtwarzacz osadzalny', @@ -211,4 +214,14 @@ return [ 'light' => 'Jasny', 'light-transparent' => 'Jasny przezroczysty', ], + 'publication_status_banner' => [ + 'draft_mode' => 'tryb szkicu', + 'text' => '{publication_status, select, + published {Ten odcinek jeszcze nie został opublikowany.} + scheduled {Ten odcinek zostanie opublikowany {publication_date}.} + with_podcast {Ten odcinek zostanie opublikowany w tym samym momencie, co podcast.} + other {Ten odcinek jeszcze nie został opublikowany.} + }', + 'preview' => 'Podgląd', + ], ]; diff --git a/modules/Admin/Language/pl/EpisodeNavigation.php b/modules/Admin/Language/pl/EpisodeNavigation.php index 6d71ae7e..48564e3d 100644 --- a/modules/Admin/Language/pl/EpisodeNavigation.php +++ b/modules/Admin/Language/pl/EpisodeNavigation.php @@ -10,11 +10,11 @@ declare(strict_types=1); return [ 'go_to_page' => 'Wyświetl stronę odcinka', - 'dashboard' => 'Pulpit odcinka', + 'dashboard' => 'Panel odcinka', 'episode-view' => 'Początek', 'episode-edit' => 'Edytuj odcinek', 'episode-persons-manage' => 'Zarządzaj osobami', - 'embed-add' => 'Odtwarzacz do zagnieżdżenia', + 'embed-add' => 'Odtwarzacz do osadzania', 'clips' => 'Klipy', 'video-clips-list' => 'Klipy wideo', 'video-clips-create' => 'Nowy klip wideo', diff --git a/modules/Admin/Language/pl/Fediverse.php b/modules/Admin/Language/pl/Fediverse.php index e35653ae..8c0bda3c 100644 --- a/modules/Admin/Language/pl/Fediverse.php +++ b/modules/Admin/Language/pl/Fediverse.php @@ -10,9 +10,9 @@ declare(strict_types=1); return [ 'messages' => [ - 'actorNotFound' => 'The account could not be found!', + 'actorNotFound' => 'Nie udało się znaleźć użytkownika!', 'blockActorSuccess' => '{actor} został zablokowany!', - 'unblockActorSuccess' => 'Actor został odblokowany!', + 'unblockActorSuccess' => 'Aktor został odblokowany!', 'blockDomainSuccess' => '{domain} została zablokowana!', 'unblockDomainSuccess' => '{domain} została odblokowana!', ], @@ -20,7 +20,7 @@ return [ 'blocked_domains' => 'Zablokowane domeny', 'block_lists_form' => [ 'handle' => 'Uchwyt konta', - 'handle_hint' => 'Wpisz @nazwęużytkownika@domenę konta.', + 'handle_hint' => 'Wpisz @nazwaużytkownika@domena konta.', 'domain' => 'Nazwa domeny', 'submit' => 'Zablokuj!', ], diff --git a/modules/Admin/Language/pl/Install.php b/modules/Admin/Language/pl/Install.php index bb333f22..28724a3e 100644 --- a/modules/Admin/Language/pl/Install.php +++ b/modules/Admin/Language/pl/Install.php @@ -15,15 +15,15 @@ return [ 'form' => [ 'instance_config' => 'Konfiguracja instancji', 'hostname' => 'Nazwa hosta', - 'media_base_url' => 'Bazowy URL multimediów', + 'media_base_url' => 'Bazowy URL mediów', 'media_base_url_hint' => - 'Jeśli korzystasz z CDN i/lub zewnętrznej usługi analitycznej, możesz ustawić je tutaj.', - 'admin_gateway' => 'Brama administracyjna', + 'Jeśli korzystasz z CDNa i/lub zewnętrznej usługi analitycznej, możesz ustawić je tutaj.', + 'admin_gateway' => 'Strona administracyjna', 'admin_gateway_hint' => - 'Droga dostępu do obszaru administracyjnego (np. https://example.com/cp-admin). Domyślnie jest ustawiona jako cp-admin, ze względów bezpieczeństwa zalecamy jej zmianę.', - 'auth_gateway' => 'Brama uwierzytelniania', + 'Droga dostępu do obszaru administracyjnego (np. https://example.com/cp-admin). Domyślnie jest ustawiona jako cp-admin, ale ze względów bezpieczeństwa zalecamy zmianę tej nazwy.', + 'auth_gateway' => 'Strona uwierzytelniania', 'auth_gateway_hint' => - 'Droga dostępu do stron uwierzytelniających (np. https://example.com/cp-auth). Domyślnie jest ustawiona jako cp-auth, ze względów bezpieczeństwa zalecamy jej zmianę.', + 'Dostęp do stron uwierzytelniających (np. https://example.com/cp-auth). Domyślnie jest ustawiony jako cp-auth, ale ze względów bezpieczeństwa zalecamy zmianę tej nazwy.', 'database_config' => 'Konfiguracja bazy danych', 'database_config_hint' => 'Castopod musi połączyć się z bazą danych MySQL (lub MariaDB). Jeśli nie masz tych wymaganych informacji, skontaktuj się z administratorem serwera.', @@ -33,11 +33,11 @@ return [ 'db_password' => 'Hasło bazy danych', 'db_prefix' => 'Prefiks bazy danych', 'db_prefix_hint' => - "Przedrostek nazw tabel Castopod; pozostaw bez zmian jeśli nie wiesz, co to znaczy.", + "Prefiks nazw tabel Castopod — pozostaw bez zmian, jeśli nie wiesz, co to znaczy.", 'cache_config' => 'Konfiguracja pamięci podręcznej', 'cache_config_hint' => - 'Wybierz preferowaną obsługę pamięci podręcznej (cache). Pozostaw to jako wartość domyślną, jeśli nie masz pojęcia, co to znaczy.', - 'cache_handler' => 'Obsługa pamięci podręcznej', + 'Wybierz preferowany mechanizm pamięci podręcznej. Zostaw domyślną wartość, jeśli nie masz pojęcia, co to znaczy.', + 'cache_handler' => 'Mechanizm pamięci podręcznej', 'cacheHandlerOptions' => [ 'file' => 'Plik', 'redis' => 'Redis', @@ -56,6 +56,6 @@ return [ 'databaseConnectError' => 'Castopod nie mógł połączyć się z Twoją bazą danych. Edytuj konfigurację bazy danych i spróbuj ponownie.', 'writeError' => - "Nie można utworzyć/zapisać pliku `.env`. Musisz go utworzyć ręcznie postępując zgodnie z szablonem pliku `.env.example` w pakiecie Castopod.", + "Nie można utworzyć/zapisać pliku `.env`. Musisz go utworzyć ręcznie, postępując zgodnie z szablonem `.env.example` w pakiecie Castopod.", ], ]; diff --git a/modules/Admin/Language/pl/Navigation.php b/modules/Admin/Language/pl/Navigation.php index d4a44104..c7c14989 100644 --- a/modules/Admin/Language/pl/Navigation.php +++ b/modules/Admin/Language/pl/Navigation.php @@ -11,17 +11,19 @@ declare(strict_types=1); return [ 'toggle_sidebar' => 'Przełącz pasek boczny', 'go_to_website' => 'Idź do strony internetowej', - 'go_to_admin' => 'Idź do administracji', - 'dashboard' => 'Pulpit', - 'admin' => 'Początek', + 'go_to_admin' => 'Przejdź do panelu administratora', + 'not-authorized' => 'Brak uprawnień', + 'dashboard' => 'Panel', + 'admin' => 'Strona główna', 'podcasts' => 'Podcasty', 'podcast-list' => 'Wszystkie podcasty', 'podcast-create' => 'Nowy podcast', - 'podcast-import' => 'Importuj podcast', + 'all-podcast-imports' => 'Wszystkie importy podcastów', + 'podcast-imports-add' => 'Importuj podcast', 'persons' => 'Osoby', 'person-list' => 'Wszystkie osoby', 'person-create' => 'Nowa osoba', - 'fediverse' => 'Fediverse', + 'fediverse' => 'Fediwersum', 'fediverse-blocked-actors' => 'Zablokowane konta', 'fediverse-blocked-domains' => 'Zablokowane domeny', 'users' => 'Użytkownicy', @@ -33,6 +35,7 @@ return [ 'settings' => 'Ustawienia', 'settings-general' => 'Ogólne', 'settings-theme' => 'Motyw', + 'admin-about' => 'Informacje', 'account' => [ 'my-account' => 'Moje konto', 'change-password' => 'Zmień hasło', diff --git a/modules/Admin/Language/pl/Notifications.php b/modules/Admin/Language/pl/Notifications.php index 2b139d51..22d4fd6c 100644 --- a/modules/Admin/Language/pl/Notifications.php +++ b/modules/Admin/Language/pl/Notifications.php @@ -9,11 +9,11 @@ declare(strict_types=1); */ return [ - 'title' => 'Notifications', - 'reply' => '{actor_username} replied to your post', - 'favourite' => '{actor_username} favourited your post', - 'reblog' => '{actor_username} shared your post', - 'follow' => '{actor_username} started following you', - 'no_notifications' => 'No notifications', - 'mark_all_as_read' => 'Mark all as read', + 'title' => 'Powiadomienia', + 'reply' => '{actor_username} odpowiedział na Twój post', + 'favourite' => '{actor_username} polubił Twój post', + 'reblog' => '{actor_username} udostępnił Twój post', + 'follow' => '{actor_username} zaczął Cię obserwować', + 'no_notifications' => 'Brak powiadomień', + 'mark_all_as_read' => 'Oznacz wszystkie jako przeczytane', ]; diff --git a/modules/Admin/Language/pl/Person.php b/modules/Admin/Language/pl/Person.php index baf67094..0386036c 100644 --- a/modules/Admin/Language/pl/Person.php +++ b/modules/Admin/Language/pl/Person.php @@ -24,7 +24,7 @@ return [ 'form' => [ 'avatar' => 'Awatar', 'avatar_size_hint' => - 'Avatar must be squared and at least 400px wide and tall.', + 'Awatar musi być kwadratowy o szerokości i wysokości co najmniej 400 pikseli.', 'full_name' => 'Pełne imię i nazwisko', 'full_name_hint' => 'To jest pełne imię i nazwisko lub pseudonim osoby.', 'unique_name' => 'Unikalna nazwa', @@ -61,5 +61,5 @@ return [ 'submit_add' => 'Dodaj osobę(y)', 'remove' => 'Usuń', ], - 'credits' => 'Kredyty', + 'credits' => 'Autorzy', ]; diff --git a/modules/Admin/Language/pl/Platforms.php b/modules/Admin/Language/pl/Platforms.php index 82bb83e6..d6a97a84 100644 --- a/modules/Admin/Language/pl/Platforms.php +++ b/modules/Admin/Language/pl/Platforms.php @@ -9,9 +9,22 @@ declare(strict_types=1); */ return [ - 'title' => 'Platformy', + 'title' => [ + 'podcasting' => 'Platformy podcastowe', + 'social' => 'Sieci społecznościowe', + 'funding' => 'Linki do finansowania', + ], + 'website' => 'Strona', 'home_url' => 'Idź do strony {platformName}', + 'register' => 'Zarejestruj', 'submit_url' => 'Prześlij swój podcast na {platformName}', + 'your_link' => 'Twój link', + 'your_id' => [ + 'podcasting' => 'Twoje ID', + 'social' => 'Twoje ID', + 'funding' => 'Twoje CTA', + ], + 'your_cta' => 'Twoje Call To Action', 'visible' => 'Wyświetlać na stronie głównej podcastu?', 'on_embed' => 'Wyświetlać w osadzalnym odtwarzaczu?', 'remove' => 'Usuń {platformName}', diff --git a/modules/Admin/Language/pl/Podcast.php b/modules/Admin/Language/pl/Podcast.php index 5aa92ff8..7d91fe7f 100644 --- a/modules/Admin/Language/pl/Podcast.php +++ b/modules/Admin/Language/pl/Podcast.php @@ -13,54 +13,58 @@ return [ 'no_podcast' => 'Nie znaleziono podcastu!', 'create' => 'Stwórz podcast', 'import' => 'Importuj podcast', + 'all_imports' => 'Importy podcastów', 'new_episode' => 'Nowy Odcinek', 'view' => 'Wyświetl podcast', 'edit' => 'Edytuj podcast', - 'publish' => 'Publish podcast', - 'publish_edit' => 'Edit publication', + 'publish' => 'Opublikuj podcast', + 'publish_edit' => 'Edytuj publikację', 'delete' => 'Usuń podcast', 'see_episodes' => 'Zobacz odcinki', 'see_contributors' => 'Zobacz kontrybutorów', + 'monetization_other' => 'Inna monetyzacja', 'go_to_page' => 'Idź do strony', 'latest_episodes' => 'Najnowsze odcinki', 'see_all_episodes' => 'Zobacz wszystkie odcinki', - 'draft' => 'Draft', + 'draft' => 'Wersja robocza', 'messages' => [ - 'createSuccess' => 'Podcast successfully created!', + 'createSuccess' => 'Podcast został pomyślnie utworzony!', 'editSuccess' => 'Podcast został pomyślnie zaktualizowany!', 'importSuccess' => 'Podcast został pomyślnie zaimportowany!', - 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', - 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, - cover {cover} - banner {banner} - other {media} - }.', - 'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select, - transcript {transcript} - chapters {chapters} - image {cover} + 'deleteSuccess' => 'Podcast @{podcast_handle} został pomyślnie usunięty!', + 'deletePodcastMediaError' => 'Nie udało się usunąć {type, select, + cover {okładki} + banner {baneru} + other {mediów} + } podcastu.', + 'deleteEpisodeMediaError' => 'Nie udało się usunąć {episode_slug} {type, select, + transcript {transkrypcji} + chapters {rozdziału} + image {okładki} audio {audio} - other {media} - }.', - 'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.', - 'podcastFeedUpdateSuccess' => 'Successful update: {number_of_new_episodes, plural, - one {# episode was} - other {# episodes were} - } added to the podcast!', - 'podcastFeedUpToDate' => 'Podcast is already up to date.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', - 'publishError' => 'This podcast is either already published or scheduled for publication.', - 'publishEditError' => 'This podcast is not scheduled for publication.', - 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', - 'scheduleDateError' => 'Schedule date must be set!', + other {mediów} + } odcinka.', + 'deletePodcastMediaFolderError' => 'Nie udało się usunąć folderu z mediami podcastu {folder_path}. Możesz go ręcznie usunąć ze swojego dysku.', + 'podcastFeedUpdateSuccess' => 'Zaktualizowano pomyślnie: {number_of_new_episodes, plural, + one {# odcinek został dodany} + few {# odcinki zostały dodane} + other {# odcinków zostało dodanych} + } do podcastu!', + 'podcastFeedUpToDate' => 'Podcast jest już aktualny.', + 'publishError' => 'Ten podcast jest już opublikowany lub zaplanowany do publikacji.', + 'publishEditError' => 'Ten podcast nie jest zaplanowany do publikacji.', + 'publishCancelSuccess' => 'Publikacja odcinka pomyślnie anulowana!', + 'scheduleDateError' => 'Zaplanowana data musi być ustawiona!', ], 'form' => [ 'identity_section_title' => 'Tożsamość podcastu', 'identity_section_subtitle' => 'Te pola pozwalają Ci zostać zauważonym.', + 'fediverse_section_title' => 'Tożsamość w Fediwersum', + 'cover' => 'Okładka podcastu', - 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'cover_size_hint' => 'Okładka musi być kwadratowa oraz mieć co najmniej 1400px wysokości i szerokości.', 'banner' => 'Baner podcastu', - 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', + 'banner_size_hint' => 'Banner musi mieć proporcje 3:1 oraz mieć co najmniej 1500px szerokości.', 'banner_delete' => 'Usuń baner podcastu', 'title' => 'Tytuł', 'handle' => 'Uchwyt', @@ -71,7 +75,17 @@ return [ 'episodic' => 'Epizodyczny', 'episodic_hint' => 'Jeśli odcinki mają być pobierane bez określonej kolejności. Najnowsze odcinki zostaną zaprezentowane jako pierwsze.', 'serial' => 'Seryjny', - 'serial_hint' => 'Jeśli odcinki mają być pobierane w kolejności sekwencyjnej. Jako pierwsze zostaną zaprezentowane najstarsze odcinki.', + 'serial_hint' => 'Jeśli odcinki są przeznaczone do wykorzystania w kolejności sekwencyjnej. Odcinki będą wyświetlane w kolejności numerycznej.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium reprezentowane przez tag podcast:medium w RSS. Zmiana tego może mieć wpływ na wygląd w odtwarzaczach podcastów.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Opisuje kanał dla podcastu.', + 'music' => 'Muzyka', + 'music_hint' => 'Kanał muzyki zorganizowany w "album" z każdym elementem jako utwór w albumie.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specyficzne typy audio z jednym elementem na kanał lub gdzie każdy element reprezentuje rozdział książki.', ], 'description' => 'Opis', 'classification_section_title' => 'Klasyfikacja', @@ -96,9 +110,11 @@ return [ 'owner_email' => 'Email właściciela', 'owner_email_hint' => 'Będzie używany przez większość platform do weryfikacji własności podcastu. Widoczne w publicznym kanale RSS.', + 'is_owner_email_removed_from_feed' => 'Usuń email właściciela z publicznego kanału RSS', + 'is_owner_email_removed_from_feed_hint' => 'Może być konieczne tymczasowe odkrycie adresu e-mail, aby katalog mógł zweryfikować właściciela podcastu.', 'publisher' => 'Wydawca', 'publisher_hint' => - 'Grupa odpowiedzialna za stworzenie programu. Często odnosi się do firmy macierzystej lub sieci podcastów. To pole jest czasami oznaczone jako ’Autor’.', + 'Grupa odpowiedzialna za stworzenie programu. Często odnosi się do firmy macierzystej lub sieci podcastów. To pole jest czasami oznaczone jako "Autor".', 'copyright' => 'Prawa autorskie', 'location_section_title' => 'Lokalizacja', 'location_section_subtitle' => 'O jakim miejscu jest ten podcast?', @@ -108,8 +124,13 @@ return [ 'monetization_section_subtitle' => 'Zarabiaj dzięki swoim odbiorcom.', 'premium' => 'Premium', - 'premium_by_default' => 'Episodes must be set as premium by default', - 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'premium_by_default' => 'Odcinki muszą być domyślnie ustawione jako premium', + 'premium_by_default_hint' => 'Odcinki podcastów będą domyślnie oznaczone jako premium. Nadal możesz ustawić niektóre odcinki, zwiastuny lub bonusy jako publiczne.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Odwiedź panel OP3 (link zewnętrzny)', + 'op3_hint' => 'Oceń swoje dane analityczne z OP3, otwartej i zaufanej usługi strony trzeciej. Udostępnij, weryfikuj i porównuj swoje dane analityczne za pomocą ekosystemu otwartego podcastingu.', + 'op3_enable' => 'Włącz usługę analityczną OP3', + 'op3_enable_hint' => 'Ze względów bezpieczeństwa dane analityczne odcinków premium nie będą udostępniane OP3.', 'payment_pointer' => 'Wskaźnik płatności do zarabiania w sieci', 'payment_pointer_hint' => 'To tutaj otrzymasz pieniądze dzięki Monetyzacji Internetowej', @@ -117,12 +138,13 @@ return [ 'advanced_section_subtitle' => 'Jeśli potrzebujesz tagów RSS, których Castopod nie obsługuje, ustaw je tutaj.', 'custom_rss' => 'Własne tagi RSS dla podcastu', - 'custom_rss_hint' => 'Zostaną wstawione w tagu ❬channel❭.', + 'custom_rss_hint' => 'Zostanie wstawione w tagu ❬channel❭.', + 'verify_txt' => 'Weryfikacja własności TXT', + 'verify_txt_hint' => 'Zamiast polegać na e-mailu, niektóre usługi firm trzecich mogą potwierdzić twoje prawa własności podcastu, prosząc Cię o osadzenie tekstu weryfikacyjnego w twoim kanale.', + 'verify_txt_helper' => 'Ten tekst jest wstrzykiwany do znacznika .', 'new_feed_url' => 'Nowy adres URL kanału', 'new_feed_url_hint' => 'Użyj tego pola, gdy przenosisz się do innej domeny lub platformy hostingowej podcastu. Domyślnie wartość jest ustawiona na bieżący adres URL RSS, jeśli podcast jest importowany.', - 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', + 'old_feed_url' => 'Stary URL kanału', 'partnership' => 'Partnerstwo', 'partner_id' => 'ID', 'partner_link_url' => 'Adres URL linku', @@ -130,10 +152,9 @@ return [ 'partner_id_hint' => 'Twój własny ID partnera', 'partner_link_url_hint' => 'Ogólny adres linku partnera', 'partner_image_url_hint' => 'Ogólny adres obrazu partnera', - 'status_section_title' => 'Status', - 'block' => 'Podcast should be hidden from public catalogues', + 'block' => 'Odcinek powinien być ukryty w publicznych katalogach', 'block_hint' => - 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'Pokazywanie lub ukrywanie odcinka: przełączanie tej funkcji zapobiega pojawieniu się odcinka w Apple Podcasts, Google Podcasts, a także w aplikacjach innych firm, które pobierają z tych katalogów. (Niegwarantowane)', 'complete' => 'Podcast nie będzie miał nowych odcinków', 'lock' => 'Zapobiegaj kopiowaniu podcastu', 'lock_hint' => @@ -242,49 +263,49 @@ return [ 'hockey' => 'Hokej', 'rugby' => 'Rugby', 'running' => 'Bieg', - 'soccer' => 'Soccer', + 'soccer' => 'Piłka nożna', 'swimming' => 'Pływanie', 'tennis' => 'Tenis', 'volleyball' => 'Siatkówka', - 'wilderness' => 'Wilderness', - 'wrestling' => 'Wrestling', - 'after_shows' => 'After Shows', + 'wilderness' => 'Dzika przyroda', + 'wrestling' => 'Zapasy', + 'after_shows' => 'Po audycji', 'film_history' => 'Historia Filmu', 'film_interviews' => 'Wywiady filmowe', 'film_reviews' => 'Recenzje filmów', 'tv_reviews' => 'Recenzje telewizyjne', ], 'publish_form' => [ - 'back_to_podcast_dashboard' => 'Back to podcast dashboard', - 'post' => 'Your announcement post', + 'back_to_podcast_dashboard' => 'Powrót do panelu podcastów', + 'post' => 'Twój wpis ogłoszeniowy', 'post_hint' => - "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", - 'message_placeholder' => 'Write your message…', - 'submit' => 'Publish', - 'publication_date' => 'Publication date', + "Napisz wiadomość, aby ogłosić publikację podcastu. Wiadomość będzie wyświetlana na stronie głównej podcastu.", + 'message_placeholder' => 'Napisz swoją wiadomość…', + 'submit' => 'Opublikuj', + 'publication_date' => 'Data publikacji', 'publication_method' => [ - 'now' => 'Now', - 'schedule' => 'Schedule', + 'now' => 'Teraz', + 'schedule' => 'Zaplanuj', ], - 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date' => 'Planowana data publikacji', 'scheduled_publication_date_hint' => - 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', - 'submit_edit' => 'Edit publication', - 'cancel_publication' => 'Cancel publication', - 'message_warning' => 'You did not write a message for your announcement post!', - 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', - 'message_warning_submit' => 'Publish anyway', + 'Możesz zaplanować wydanie odcinka, ustawiając przyszłą datę publikacji. To pole musi być sformatowane jako YYYY-MM-DD HH:mm', + 'submit_edit' => 'Edytuj publikację', + 'cancel_publication' => 'Anuluj publikację', + 'message_warning' => 'Nie napisałeś wiadomości do swojego wpisu ogłoszeniowego!', + 'message_warning_hint' => 'Posiadanie wiadomości zwiększa zaangażowanie społeczne, co skutkuje lepszą widocznością Twojego podcastu.', + 'message_warning_submit' => 'Opublikuj mimo to', ], 'publication_status_banner' => [ - 'draft_mode' => 'draft mode', - 'not_published' => 'This podcast is not yet published.', - 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + 'draft_mode' => 'tryb szkicu', + 'not_published' => 'Ten podcast nie został jeszcze opublikowany.', + 'scheduled' => 'Ten podcast jest zaplanowany do publikacji na {publication_date}.', ], 'delete_form' => [ 'disclaimer' => - "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", - 'understand' => 'I understand, I want the podcast to be permanently deleted', - 'submit' => 'Delete', + "Usunięcie podcastu spowoduje usunięcie wszystkich odcinków, plików multimedialnych, postów i analityk z nim związanych. Ta akcja jest nieodwracalna, nie będziesz w stanie odzyskać tego wszystkiego później.", + 'understand' => 'Rozumiem, chciałbym, aby podcast został trwale usunięty', + 'submit' => 'Usuń', ], 'by' => 'Przez {publisher}', 'season' => 'Sezon {seasonNumber}', @@ -294,12 +315,14 @@ return [ 'no_episode' => 'Nie znaleziono odcinków!', 'follow' => 'Obserwuj', 'followers' => '{numberOfFollowers, plural, - one {# follower} - other {# followers} + one {# polubienie} + few {# polubienia} + other {# polubień} }', 'posts' => '{numberOfPosts, plural, - one {# post} - other {# posts} + one {# osoba} + few {# osoby} + other {# osób} }', 'activity' => 'Aktywność', 'episodes' => 'Odcinki', diff --git a/modules/Admin/Language/pl/PodcastImport.php b/modules/Admin/Language/pl/PodcastImport.php deleted file mode 100644 index a68c6d0a..00000000 --- a/modules/Admin/Language/pl/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'Podcast do zaimportowania', - 'old_podcast_section_subtitle' => - 'Upewnij się, że masz prawa do tego podcastu zanim go zaimportujesz. Kopiowanie i nadawanie podcastu bez odpowiednich praw jest piractwem i podlega ściganiu.', - 'imported_feed_url' => 'Adres URL kanału', - 'imported_feed_url_hint' => 'Kanał musi być w formacie xml lub rss.', - 'new_podcast_section_title' => 'Nowy podcast', - 'advanced_params_section_title' => 'Parametry zaawansowane', - 'advanced_params_section_subtitle' => - 'Zachowaj wartości domyślne jeśli nie masz pojęcia, do czego służą te pola.', - 'slug_field' => 'Pole używane do obliczenia slugu odcinka', - 'description_field' => - 'Pole źródłowe używane do opisu odcinka/notatek programu', - 'force_renumber' => 'Wymuś przenumerowanie odcinków', - 'force_renumber_hint' => - 'Użyj tego, jeśli Twój podcast nie ma numerów odcinków, ale chcesz je ustawić podczas importu.', - 'season_number' => 'Numer sezonu', - 'season_number_hint' => - 'Użyj tego, jeśli Twój podcast nie ma numeru sezonu, ale chcesz go ustawić podczas importu. W przeciwnym razie pozostaw pusty.', - 'max_episodes' => 'Maksymalna liczba odcinków do zaimportowania', - 'max_episodes_hint' => 'Pozostaw puste, aby zaimportować wszystkie odcinki', - 'lock_import' => - 'Ten kanał jest chroniony. Nie możesz go zaimportować. Jeśli jesteś jego właścicielem - usuń ochronę na platformie, z której pochodzi.', - 'submit' => 'Importuj podcast', -]; diff --git a/modules/Admin/Language/pl/PodcastNavigation.php b/modules/Admin/Language/pl/PodcastNavigation.php index 7f3327f1..ff8066e2 100644 --- a/modules/Admin/Language/pl/PodcastNavigation.php +++ b/modules/Admin/Language/pl/PodcastNavigation.php @@ -10,10 +10,13 @@ declare(strict_types=1); return [ 'go_to_page' => 'Idź do strony podcastu', + 'rss_feed' => 'Kanał RSS', 'dashboard' => 'Pulpit podcastu', 'podcast-view' => 'Początek', 'podcast-edit' => 'Edytuj podcast', 'podcast-persons-manage' => 'Zarządzaj osobami', + 'podcast-imports' => 'Importy podcastów', + 'podcast-imports-sync' => 'Synchronizuj kanały', 'episodes' => 'Odcinki', 'episode-list' => 'Wszystkie odcinki', 'episode-create' => 'Nowy odcinek', @@ -25,14 +28,15 @@ return [ 'podcast-analytics-players' => 'Odtwarzacze', 'podcast-analytics-listening-time' => 'Czas odsłuchu', 'podcast-analytics-time-periods' => 'Okresy czasu', - 'premium' => 'Premium', - 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', + 'monetization' => 'Monetyzacja', + 'subscription-list' => 'Wszystkie subskrypcje', + 'subscription-create' => 'Dodaj subskrypcję', 'contributors' => 'Kontrybutorzy', 'contributor-list' => 'Wszyscy kontrybutorzy', 'contributor-add' => 'Dodaj kontrybutora', - 'platforms' => 'Zewnętrzne platformy', - 'platforms-podcasting' => 'Platformy podcastingowe', + 'broadcast' => 'Transmisja', + 'platforms-podcasting' => 'Aplikacje podcastowe', 'platforms-social' => 'Sieci społecznościowe', - 'platforms-funding' => 'Finansowanie', + 'platforms-funding' => 'Linki do finansowania', + 'podcast-monetization-other' => 'Inne', ]; diff --git a/modules/Admin/Language/pl/Settings.php b/modules/Admin/Language/pl/Settings.php index 09bcfde0..ef20ebf1 100644 --- a/modules/Admin/Language/pl/Settings.php +++ b/modules/Admin/Language/pl/Settings.php @@ -15,7 +15,7 @@ return [ 'site_icon' => 'Ikona witryny', 'site_icon_delete' => 'Usuń ikonę witryny', 'site_icon_hint' => 'Ikony witryny są widoczne na kartach przeglądarki, paskach zakładek oraz po dodaniu witryny jako skrótu na urządzeniach mobilnych.', - 'site_icon_helper' => 'Icon must be squared and at least 512px wide and tall.', + 'site_icon_helper' => 'Ikona musi być kwadratowa o szerokości i wysokości co najmniej 512 pikseli.', 'site_name' => 'Nazwa strony', 'site_description' => 'Opis strony', 'submit' => 'Zapisz', @@ -35,8 +35,8 @@ return [ 'reset_counts_helper' => 'Ta opcja zresetuje i ponownie obliczy wszystkie liczniki danych (liczbę obserwujących, wpisów, komentarzy, …).', 'rewrite_media' => 'Przepisz metadane multimediów', 'rewrite_media_helper' => 'Ta opcja usunie wszystkie zbędne pliki multimedialne i odtworzy je (obrazy, pliki audio, transkrypcje, rozdziały, …)', - 'rename_episodes_files' => 'Rename episode audio files', - 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', + 'rename_episodes_files' => 'Zmień nazwę plików audio odcinka', + 'rename_episodes_files_hint' => 'Ta opcja zmieni nazwę wszystkich odcinków plików audio na losowy ciąg znaków. Użyj tego, jeśli jeden z Twoich prywatnych odcinków został ujawniony, ponieważ to skutecznie go ukryje.', 'clear_cache' => 'Wyczyść całą pamięć podręczną', 'clear_cache_helper' => 'Ta opcja opróżni pamięć podręczną (cache) redis lub zapisywalne/buforowane pliki.', 'run' => 'Przeprowadź porządkowanie', @@ -48,7 +48,7 @@ return [ 'accent_section_subtitle' => 'Wybierz kolor, aby określić wygląd i styl wszystkich stron publicznych.', 'pine' => 'Sosna', 'crimson' => 'Karmazynowy', - 'amber' => 'bursztynowy', + 'amber' => 'Bursztyn', 'lake' => 'Jezioro', 'jacaranda' => 'Jacaranda', 'onyx' => 'Onyks', diff --git a/modules/Admin/Language/pl/Soundbite.php b/modules/Admin/Language/pl/Soundbite.php index 97544f8d..32fbc02b 100644 --- a/modules/Admin/Language/pl/Soundbite.php +++ b/modules/Admin/Language/pl/Soundbite.php @@ -20,7 +20,7 @@ return [ 'form' => [ 'title' => 'Nowa zajawka', 'soundbite_title' => 'Tytuł zajawki', - 'start_time' => 'Rozpocznij w', + 'start_time' => 'Rozpocznij od', 'duration' => 'Długość', 'submit' => 'Stwórz zajawkę', ], diff --git a/modules/Admin/Language/pl/Validation.php b/modules/Admin/Language/pl/Validation.php index a71b5913..258f7119 100644 --- a/modules/Admin/Language/pl/Validation.php +++ b/modules/Admin/Language/pl/Validation.php @@ -10,9 +10,8 @@ declare(strict_types=1); return [ 'min_dims' => - '{field} nie jest obrazem, albo nie jest wystarczająco szeroki lub wysoki.', + '{field} nie jest obrazem albo nie jest wystarczająco szeroki lub wysoki.', 'is_image_ratio' => - '{field} nie jest obrazem, albo nie ma właściwych proporcji.', - 'validate_url' => - 'Pole {field} musi być prawidłowym adresem URL (np. https://przyklad.com/).', + '{field} nie jest obrazem albo nie ma właściwych proporcji.', + 'is_json' => '{field} zawiera nieprawidłowy JSON.', ]; diff --git a/modules/Admin/Language/pl/VideoClip.php b/modules/Admin/Language/pl/VideoClip.php index 6ff09752..03453b0e 100644 --- a/modules/Admin/Language/pl/VideoClip.php +++ b/modules/Admin/Language/pl/VideoClip.php @@ -17,10 +17,10 @@ return [ 'queued_hint' => 'Klip czeka na przetworzenie.', 'pending' => 'oczekuje', 'pending_hint' => 'Klip zostanie wkrótce wygenerowany.', - 'running' => 'działanie', + 'running' => 'w toku', 'running_hint' => 'Klip jest generowany.', 'failed' => 'niepowodzenie', - 'failed_hint' => 'Nie można wygenerować klipu: błąd skryptu.', + 'failed_hint' => 'Nie można było wygenerować klipu: błąd skryptu.', 'passed' => 'powodzenie', 'passed_hint' => 'Klip został pomyślnie wygenerowany!', ], @@ -35,7 +35,7 @@ return [ 'delete' => 'Usuń klip', 'logs' => 'Dzienniki zadania', 'messages' => [ - 'alreadyExistingError' => 'Klip wideo, który próbujesz utworzyć już istnieje!', + 'alreadyExistingError' => 'Klip wideo, który próbujesz utworzyć, już istnieje!', 'addToQueueSuccess' => 'Klip wideo został dodany do kolejki i oczekuje na utworzenie!', 'deleteSuccess' => 'Klip wideo został pomyślnie usunięty!', ], @@ -51,11 +51,11 @@ return [ 'format' => [ 'label' => 'Wybierz format', 'landscape_hint' => 'W proporcji 16:9, filmy w orientacji poziomej są świetne do PeerTube, Youtube i Vimeo.', - 'portrait_hint' => 'W proporcji 9:16, filmy pionowe świetnie nadają się do TikTok, krótkich filmów na YouTube i historii na Instagramie.', + 'portrait_hint' => 'W proporcji 9:16, filmy pionowe świetnie nadają się do TikTok, krótkich filmów na YouTube i Stories na Instagramie.', 'squared_hint' => 'W proporcji 1:1, kwadratowe filmy są świetne dla Mastodon, Facebooka, Twittera i LinkedIn.', ], 'theme' => 'Wybierz motyw', - 'start_time' => 'Rozpocznij w', + 'start_time' => 'Rozpocznij od', 'duration' => 'Długość', 'trim_start' => 'Przytnij początek', 'trim_end' => 'Przytnij koniec', @@ -63,10 +63,10 @@ return [ ], 'requirements' => [ 'title' => 'Brakujące wymagania', - 'missing' => 'Brakuje wymagań. Upewnij się, że dodałeś wszystkie wymagane elementy, aby móc tworzyć wideo do tego odcinka!', + 'missing' => 'Brakuje wymaganych elementów. Upewnij się, że dodałeś wszystkie wymagane elementy, aby móc tworzyć wideo do tego odcinka!', 'ffmpeg' => 'FFmpeg', 'gd' => 'Graphics Draw (GD)', - 'freetype' => 'Freetype library dla GD', + 'freetype' => 'Biblioteka Freetype dla GD', 'transcript' => 'Plik z transkrypcją (.srt)', ], ]; diff --git a/modules/Admin/Language/pt-BR/PodcastImport.php b/modules/Admin/Language/pt-BR/PodcastImport.php deleted file mode 100644 index 97dd95c2..00000000 --- a/modules/Admin/Language/pt-BR/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'Este procedimento pode levar muito tempo. Como a versão atual não mostra nenhum progresso durante a execução, você não verá nada atualizado até que seja finalizado. Em caso de erro de tempo limite, aumente o valor de `max_execution_time`.', - 'old_podcast_section_title' => 'O podcast para importar', - 'old_podcast_section_subtitle' => - 'Certifique-se de possuir os direitos para esse podcast antes de importá-lo. Copiar e transmitir um podcast sem os direitos adequados é uma pirataria e corre o risco de ser processado.', - 'imported_feed_url' => 'URL do feed', - 'imported_feed_url_hint' => 'O feed deve estar no formato xml ou rss.', - 'new_podcast_section_title' => 'O novo podcast', - 'advanced_params_section_title' => 'Parâmetros avançados', - 'advanced_params_section_subtitle' => - 'Mantenha os valores padrão se você não tiver ideia do que os campos servem.', - 'slug_field' => 'Campo a ser usado para calcular o slug do episódio', - 'description_field' => - 'Campo de origem usado para descrição do episódio / mostrar notas', - 'force_renumber' => 'Forçar renumeração de episódios', - 'force_renumber_hint' => - 'Use isto se seu podcast não tem números de episódio, mas deseja configurá-los durante a importação.', - 'season_number' => 'Número da temporada', - 'season_number_hint' => - 'Use isto se o seu podcast não tem um número de temporada, mas deseja definir um durante a importação. Deixe em branco caso contrário.', - 'max_episodes' => 'Número máximo de episódios para importar', - 'max_episodes_hint' => 'Deixe em branco para importar todos os episódios', - 'lock_import' => - 'Este feed está protegido. Você não pode importá-lo. Se você é o proprietário, desproteja-o na plataforma de origem.', - 'submit' => 'Importar podcast', -]; diff --git a/modules/Admin/Language/pt-br/AboutCastopod.php b/modules/Admin/Language/pt-br/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/pt-br/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/pt-BR/Breadcrumb.php b/modules/Admin/Language/pt-br/Breadcrumb.php similarity index 75% rename from modules/Admin/Language/pt-BR/Breadcrumb.php rename to modules/Admin/Language/pt-br/Breadcrumb.php index 028fb11c..7ca40d76 100644 --- a/modules/Admin/Language/pt-BR/Breadcrumb.php +++ b/modules/Admin/Language/pt-br/Breadcrumb.php @@ -14,29 +14,34 @@ return [ ->gateway => 'Início', 'podcasts' => 'podcasts', 'episodes' => 'episódios', - 'subscriptions' => 'subscriptions', + 'subscriptions' => 'assinaturas', 'contributors' => 'contribuidores', 'pages' => 'páginas', 'settings' => 'configurações', 'theme' => 'tema', + 'about' => 'sobre', 'add' => 'adicionar', 'new' => 'novo', 'edit' => 'editar', 'persons' => 'pessoas', 'publish' => 'publicar', 'publish-edit' => 'editar publicação', - 'publish-date-edit' => 'edit publication date', + 'publish-date-edit' => 'editar data de publicação', 'unpublish' => 'despublicar', 'delete' => 'excluir', + 'remove' => 'remover', 'fediverse' => 'fediverso', - 'block-lists' => 'listas de bloqueio', + 'blocked-actors' => 'atores bloqueados', + 'blocked-domains' => 'domínios bloqueados', 'users' => 'usuários', 'my-account' => 'minha conta', 'change-password' => 'alterar senha', - 'import' => 'importar feed', + 'imports' => 'importações', + 'sync-feeds' => 'synchronize feeds', 'platforms' => 'plataformas', 'social' => 'redes sociais', 'funding' => 'financiamento', + 'monetization-other' => 'other monetization', 'analytics' => 'estatísticas', 'locations' => 'localizações', 'webpages' => 'páginas da web', @@ -47,6 +52,6 @@ return [ 'soundbites' => 'clipes de áudio', 'video-clips' => 'clipes de vídeo', 'embed' => 'player incorporável', - 'notifications' => 'notifications', - 'suspend' => 'suspend', + 'notifications' => 'notificações', + 'suspend' => 'suspender', ]; diff --git a/modules/Admin/Language/pt-BR/Charts.php b/modules/Admin/Language/pt-br/Charts.php similarity index 96% rename from modules/Admin/Language/pt-BR/Charts.php rename to modules/Admin/Language/pt-br/Charts.php index ff2e91d1..9a84d031 100644 --- a/modules/Admin/Language/pt-BR/Charts.php +++ b/modules/Admin/Language/pt-br/Charts.php @@ -37,4 +37,5 @@ return [ 'podcast_by_bandwidth' => 'Largura de banda usada diária (em MB)', 'total_storage_by_month' => 'Armazenamento mensal (em MB)', 'total_bandwidth_by_month' => 'Largura de banda usada mensalmente (em MB)', + 'total_bandwidth_by_month_limit' => 'Limitado a {totalBandwidth} por mês', ]; diff --git a/modules/Admin/Language/pt-BR/Common.php b/modules/Admin/Language/pt-br/Common.php similarity index 98% rename from modules/Admin/Language/pt-BR/Common.php rename to modules/Admin/Language/pt-br/Common.php index c323eef3..f82a5109 100644 --- a/modules/Admin/Language/pt-BR/Common.php +++ b/modules/Admin/Language/pt-br/Common.php @@ -40,6 +40,7 @@ return [ ], 'upload_file' => 'Enviar um arquivo', 'remote_url' => 'URL remota', + 'save' => 'Save', ], 'play_episode_button' => [ 'play' => 'Reproduzir', diff --git a/modules/Admin/Language/pt-BR/Countries.php b/modules/Admin/Language/pt-br/Countries.php similarity index 100% rename from modules/Admin/Language/pt-BR/Countries.php rename to modules/Admin/Language/pt-br/Countries.php diff --git a/modules/Admin/Language/pt-BR/Dashboard.php b/modules/Admin/Language/pt-br/Dashboard.php similarity index 100% rename from modules/Admin/Language/pt-BR/Dashboard.php rename to modules/Admin/Language/pt-br/Dashboard.php diff --git a/modules/Admin/Language/pt-BR/Episode.php b/modules/Admin/Language/pt-br/Episode.php similarity index 85% rename from modules/Admin/Language/pt-BR/Episode.php rename to modules/Admin/Language/pt-br/Episode.php index b89d4401..427f9092 100644 --- a/modules/Admin/Language/pt-BR/Episode.php +++ b/modules/Admin/Language/pt-br/Episode.php @@ -22,16 +22,17 @@ return [ 'all_podcast_episodes' => 'Todos os episódios de podcast', 'back_to_podcast' => 'Voltar para o podcast', 'edit' => 'Editar', + 'preview' => 'Pré-visualizar', 'publish' => 'Publicar', 'publish_edit' => 'Editar publicação', - 'publish_date_edit' => 'Edit publication date', + 'publish_date_edit' => 'Editar data de publicação', 'unpublish' => 'Despublicar', 'publish_error' => 'O episódio já está publicado.', 'publish_edit_error' => 'O episódio já está publicado.', 'publish_cancel_error' => 'O episódio já está publicado.', - 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', - 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', - 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'publish_date_edit_error' => 'Episódio ainda não foi publicado, você não pode editar a data de publicação dele.', + 'publish_date_edit_future_error' => 'A data de publicação do episódio só pode ser definida para data passada! Se você deseja reagendá-la, cancele a publicação primeiro.', + 'publish_date_edit_success' => 'A data de publicação do episódio foi atualizada com sucesso!', 'unpublish_error' => 'O episódio não está publicado.', 'delete' => 'Excluir', 'go_to_page' => 'Ir para a página', @@ -55,6 +56,7 @@ return [ }', 'episode' => 'Episódio', 'visibility' => 'Visibilidade', + 'downloads' => 'Downloads', 'comments' => 'Comentários', 'actions' => 'Ações', ], @@ -85,7 +87,7 @@ return [ image {capa} audio {áudio} other {mídia} - } {file_path}. Você pode removê-lo manualmente do seu disco.', + }arquivo {file_key}. Você pode removê-lo manualmente do seu disco.', 'sameSlugError' => 'Um episódio com o slug escolhido já existe.', ], 'form' => [ @@ -114,7 +116,7 @@ return [ 'bonus_hint' => 'Conteúdo extra para o podcast (por exemplo, informações nos bastidores ou entrevistas com o elenco) ou conteúdo promocional com outro podcast', ], 'premium_title' => 'Premium', - 'premium' => 'Episode must be accessible to premium subscribers only', + 'premium' => 'Episódio deve estar acessível apenas para assinantes premium', 'parental_advisory' => [ 'label' => 'Aviso aos pais', 'hint' => 'O episódio contém conteúdo explícito?', @@ -137,9 +139,9 @@ return [ 'location_name' => 'Nome ou endereço da localização', 'location_name_hint' => 'Esta pode ser uma localização real ou fictícia', 'transcript' => 'Transcrição (legendas / legendas ocultas)', - 'transcript_hint' => 'Somente .srt são permitidos.', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', 'transcript_download' => 'Baixar transcrição', - 'transcript_file' => 'Arquivo de transcrição (.srt)', + 'transcript_file' => 'Transcript file (.srt or .vtt)', 'transcript_remote_url' => 'URL remoto para transcrição', 'transcript_file_delete' => 'Excluir arquivo de transcrição', 'chapters' => 'Capítulos', @@ -183,13 +185,13 @@ return [ 'message_warning_submit' => 'Publicar mesmo assim', ], 'publish_date_edit_form' => [ - 'new_publication_date' => 'New publication date', - 'new_publication_date_hint' => 'Must be set to a past date.', - 'submit' => 'Edit publication date', + 'new_publication_date' => 'Nova data de publicação', + 'new_publication_date_hint' => 'Deve ser definido como uma data passada.', + 'submit' => 'Editar data de publicação', ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", + "O cancelamento da publicação do episódio excluirá todos os comentários e publicações associados a ele e o removerá do feed RSS do podcast.", 'understand' => 'Eu entendo, eu quero despublicar o episódio', 'submit' => 'Despublicar', ], @@ -210,4 +212,14 @@ return [ 'light' => 'Claro', 'light-transparent' => 'Claro translúcido', ], + 'publication_status_banner' => [ + 'draft_mode' => 'modo rascunho', + 'text' => '{publication_status, select, + published {Esse episódio ainda não foi publicado.} + scheduled {Esse episódio está agendado para publicação em {publication_date}.} + with_podcast {Esse episódio será publicado ao mesmo tempo que o podcast. .} + other {Esse episódio ainda não foi publicado.} + }', + 'preview' => 'Pré-visualizar', + ], ]; diff --git a/modules/Admin/Language/pt-BR/EpisodeNavigation.php b/modules/Admin/Language/pt-br/EpisodeNavigation.php similarity index 100% rename from modules/Admin/Language/pt-BR/EpisodeNavigation.php rename to modules/Admin/Language/pt-br/EpisodeNavigation.php diff --git a/modules/Admin/Language/pt-BR/Fediverse.php b/modules/Admin/Language/pt-br/Fediverse.php similarity index 100% rename from modules/Admin/Language/pt-BR/Fediverse.php rename to modules/Admin/Language/pt-br/Fediverse.php diff --git a/modules/Admin/Language/pt-BR/Home.php b/modules/Admin/Language/pt-br/Home.php similarity index 100% rename from modules/Admin/Language/pt-BR/Home.php rename to modules/Admin/Language/pt-br/Home.php diff --git a/modules/Admin/Language/pt-BR/Install.php b/modules/Admin/Language/pt-br/Install.php similarity index 100% rename from modules/Admin/Language/pt-BR/Install.php rename to modules/Admin/Language/pt-br/Install.php diff --git a/modules/Admin/Language/pt-BR/Navigation.php b/modules/Admin/Language/pt-br/Navigation.php similarity index 86% rename from modules/Admin/Language/pt-BR/Navigation.php rename to modules/Admin/Language/pt-br/Navigation.php index c0e86154..a5032c7c 100644 --- a/modules/Admin/Language/pt-BR/Navigation.php +++ b/modules/Admin/Language/pt-br/Navigation.php @@ -12,12 +12,14 @@ return [ 'toggle_sidebar' => 'Ativar/Desativar barra lateral', 'go_to_website' => 'Ir para o site', 'go_to_admin' => 'Ir para admin', + 'not-authorized' => 'Not authorized', 'dashboard' => 'Painel de controle', 'admin' => 'Início', 'podcasts' => 'Podcasts', 'podcast-list' => 'Todos os podcasts', 'podcast-create' => 'Novo podcast', - 'podcast-import' => 'Importar um podcast', + 'all-podcast-imports' => 'Todos os importações de Podcast', + 'podcast-imports-add' => 'Importar um podcast', 'persons' => 'Pessoas', 'person-list' => 'Todas as pessoas', 'person-create' => 'Nova pessoa', @@ -33,6 +35,7 @@ return [ 'settings' => 'Confirgurações', 'settings-general' => 'Geral', 'settings-theme' => 'Tema', + 'admin-about' => 'About', 'account' => [ 'my-account' => 'Minha conta', 'change-password' => 'Alterar senha', diff --git a/modules/Admin/Language/pt-br/Notifications.php b/modules/Admin/Language/pt-br/Notifications.php new file mode 100644 index 00000000..2b139d51 --- /dev/null +++ b/modules/Admin/Language/pt-br/Notifications.php @@ -0,0 +1,19 @@ + 'Notifications', + 'reply' => '{actor_username} replied to your post', + 'favourite' => '{actor_username} favourited your post', + 'reblog' => '{actor_username} shared your post', + 'follow' => '{actor_username} started following you', + 'no_notifications' => 'No notifications', + 'mark_all_as_read' => 'Mark all as read', +]; diff --git a/modules/Admin/Language/pt-BR/Page.php b/modules/Admin/Language/pt-br/Page.php similarity index 100% rename from modules/Admin/Language/pt-BR/Page.php rename to modules/Admin/Language/pt-br/Page.php diff --git a/modules/Admin/Language/pt-BR/Pager.php b/modules/Admin/Language/pt-br/Pager.php similarity index 100% rename from modules/Admin/Language/pt-BR/Pager.php rename to modules/Admin/Language/pt-br/Pager.php diff --git a/modules/Admin/Language/pt-BR/Person.php b/modules/Admin/Language/pt-br/Person.php similarity index 100% rename from modules/Admin/Language/pt-BR/Person.php rename to modules/Admin/Language/pt-br/Person.php diff --git a/modules/Admin/Language/pt-BR/Platforms.php b/modules/Admin/Language/pt-br/Platforms.php similarity index 69% rename from modules/Admin/Language/pt-BR/Platforms.php rename to modules/Admin/Language/pt-br/Platforms.php index c49bc8a2..0388f65f 100644 --- a/modules/Admin/Language/pt-BR/Platforms.php +++ b/modules/Admin/Language/pt-br/Platforms.php @@ -9,9 +9,22 @@ declare(strict_types=1); */ return [ - 'title' => 'Plataformas', + 'title' => [ + 'podcasting' => 'Plataformas de podcasting', + 'social' => 'Redes sociais', + 'funding' => 'Links de financiamento', + ], + 'website' => 'Site', 'home_url' => 'Ir para o site {platformName}', + 'register' => 'Registrar', 'submit_url' => 'Enviar seu podcast para {platformName}', + 'your_link' => 'Seu link', + 'your_id' => [ + 'podcasting' => 'Seu ID', + 'social' => 'Seu ID', + 'funding' => 'Seu CTA', + ], + 'your_cta' => 'Sua chamada para ação', 'visible' => 'Mostrar na página inicial do podcast?', 'on_embed' => 'Mostrar no player incorporável?', 'remove' => 'Remover {platformName}', diff --git a/modules/Admin/Language/pt-BR/Podcast.php b/modules/Admin/Language/pt-br/Podcast.php similarity index 87% rename from modules/Admin/Language/pt-BR/Podcast.php rename to modules/Admin/Language/pt-br/Podcast.php index c7e30aa3..2d9e8708 100644 --- a/modules/Admin/Language/pt-BR/Podcast.php +++ b/modules/Admin/Language/pt-br/Podcast.php @@ -13,6 +13,7 @@ return [ 'no_podcast' => 'Nenhum podcast encontrado!', 'create' => 'Criar podcast', 'import' => 'Importar podcast', + 'all_imports' => 'Podcast imports', 'new_episode' => 'Novo Episódio', 'view' => 'Ver podcast', 'edit' => 'Editar podcast', @@ -21,6 +22,7 @@ return [ 'delete' => 'Excluir podcast', 'see_episodes' => 'Ver episódios', 'see_contributors' => 'Ver contribuidores', + 'monetization_other' => 'Other monetization', 'go_to_page' => 'Ir para a página', 'latest_episodes' => 'Últimos episódios', 'see_all_episodes' => 'Ver todos os episódios', @@ -48,7 +50,6 @@ return [ other {# episódios foram adicionados} } ao podcast!', 'podcastFeedUpToDate' => 'O Podcast já está atualizado.', - 'podcastNotImported' => 'O Podcast não pôde ser atualizado pois não foi importado.', 'publishError' => 'Este podcast já está publicado ou agendado para publicação.', 'publishEditError' => 'Este podcast não está agendado para publicação.', 'publishCancelSuccess' => 'Publicação do Podcast cancelada com sucesso!', @@ -57,6 +58,8 @@ return [ 'form' => [ 'identity_section_title' => 'Identidade do podcast', 'identity_section_subtitle' => 'Esses campos permitem que você seja notado.', + 'fediverse_section_title' => 'Fediverse identity', + 'cover' => 'Capa do podcast', 'cover_size_hint' => 'A capa deve ser quadrada e ter pelo menos 1400px de largura e altura.', 'banner' => 'Banner do podcast', @@ -71,7 +74,17 @@ return [ 'episodic' => 'Episódico', 'episodic_hint' => 'Se os episódios são destinados a serem consumidos sem qualquer ordem específica. Os episódios mais recentes serão apresentados primeiro.', 'serial' => 'Serial', - 'serial_hint' => 'Se os episódios são destinados a serem consumidos em ordem sequencial. Os episódios mais antigos serão apresentados primeiro.', + 'serial_hint' => 'Se a intenção é que os episódios sejam consumidos em uma ordem sequencial. Episódios vão ser apresentados em uma ordem numérica.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', ], 'description' => 'Descrição', 'classification_section_title' => 'Classificação', @@ -96,6 +109,8 @@ return [ 'owner_email' => 'E-mail do proprietário', 'owner_email_hint' => 'Será utilizado pela maioria das plataformas para verificar a propriedade do podcast. Visível no feed RSS público.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', 'publisher' => 'Editora', 'publisher_hint' => 'O grupo responsável pela criação do podcast. Muitas vezes se refere à empresa-mãe ou rede de um podcast. Este campo é por vezes rotulado como "Autor".', @@ -108,8 +123,13 @@ return [ 'monetization_section_subtitle' => 'Ganhe dinheiro graças à sua audiência.', 'premium' => 'Premium', - 'premium_by_default' => 'Episodes must be set as premium by default', + 'premium_by_default' => 'Episódios devem ser definidos como premium por padrão', 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Ativar serviço de análise OP3', + 'op3_enable_hint' => 'Por razões de segurança, os dados de análise dos episódios premium não serão compartilhados com o OP3.', 'payment_pointer' => 'Endereço de pagamento (Payment Pointer) para web monetização', 'payment_pointer_hint' => 'Este é o seu lugar onde você receberá dinheiro graças à web monetização', @@ -118,11 +138,12 @@ return [ 'Se você precisa de tags RSS que Castopod não lida, defina-as aqui.', 'custom_rss' => 'Tags RSS personalizadas para o podcast', 'custom_rss_hint' => 'Isto será injetado dentro da tag ❬channel❭.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', 'new_feed_url' => 'Nova URL de feed', 'new_feed_url_hint' => 'Use este campo ao mover este podcast para outro domínio ou alterar hosts. Por padrão, este campo é preenchido com a URL do feed RSS atual se o podcast for importado.', 'old_feed_url' => 'URL de feed antigo', - 'update_feed' => 'Atualizar feed', - 'update_feed_tip' => 'Importar os últimos episódios deste podcast', 'partnership' => 'Parceria', 'partner_id' => 'ID', 'partner_link_url' => 'URL do link', @@ -130,7 +151,6 @@ return [ 'partner_id_hint' => 'Seu próprio ID de parceiro', 'partner_link_url_hint' => 'O endereço genérico de link do parceiro', 'partner_image_url_hint' => 'O endereço genérico da imagem de parceiro', - 'status_section_title' => 'Status', 'block' => 'O podcast deve ser ocultado dos catálogos públicos', 'block_hint' => 'O status do podcast: ativar isso impede que o podcast inteiro apareça em Apple Podcasts, Google Podcasts e qualquer aplicativo de terceiros que extraia programas desses diretórios. (Não garantido)', diff --git a/modules/Admin/Language/pt-BR/PodcastNavigation.php b/modules/Admin/Language/pt-br/PodcastNavigation.php similarity index 73% rename from modules/Admin/Language/pt-BR/PodcastNavigation.php rename to modules/Admin/Language/pt-br/PodcastNavigation.php index 91e41655..873954db 100644 --- a/modules/Admin/Language/pt-BR/PodcastNavigation.php +++ b/modules/Admin/Language/pt-br/PodcastNavigation.php @@ -10,10 +10,13 @@ declare(strict_types=1); return [ 'go_to_page' => 'Ir para página do podcast', + 'rss_feed' => 'RSS feed', 'dashboard' => 'Painel do podcast', 'podcast-view' => 'Início', 'podcast-edit' => 'Editar podcast', 'podcast-persons-manage' => 'Gerenciar pessoas', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', 'episodes' => 'Episódios', 'episode-list' => 'Todos os episódios', 'episode-create' => 'Novo episódio', @@ -25,14 +28,15 @@ return [ 'podcast-analytics-players' => 'Players', 'podcast-analytics-listening-time' => 'Tempo de escuta', 'podcast-analytics-time-periods' => 'Períodos de tempo', - 'premium' => 'Premium', - 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', + 'monetization' => 'Monetization', + 'subscription-list' => 'Todas as assinaturas', + 'subscription-create' => 'Add subscription', 'contributors' => 'Contribuidores', 'contributor-list' => 'Todos os contribuidores', 'contributor-add' => 'Adicionar contribuidor', - 'platforms' => 'Plataformas externas', - 'platforms-podcasting' => 'Podcasting', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', 'platforms-social' => 'Redes sociais', - 'platforms-funding' => 'Financiamento', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', ]; diff --git a/modules/Admin/Language/pt-BR/Settings.php b/modules/Admin/Language/pt-br/Settings.php similarity index 89% rename from modules/Admin/Language/pt-BR/Settings.php rename to modules/Admin/Language/pt-br/Settings.php index 84146001..f326a094 100644 --- a/modules/Admin/Language/pt-BR/Settings.php +++ b/modules/Admin/Language/pt-br/Settings.php @@ -35,8 +35,8 @@ return [ 'reset_counts_helper' => 'Esta opção irá recalcular e redefinir todas as contagens de dados (número de seguidores, publicações, comentários, …).', 'rewrite_media' => 'Reescrever metadados de mídia', 'rewrite_media_helper' => 'Esta opção apagará todos os arquivos de mídia desnecessários e os recriará (imagens, arquivos de áudio, transcrições, capítulos, …)', - 'rename_episodes_files' => 'Rename episode audio files', - 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', + 'rename_episodes_files' => 'Renomear os arquivos de áudio de episódios', + 'rename_episodes_files_hint' => 'Esta opção irá renomear todos os episódios de arquivos de áudio para uma sequência aleatória de caracteres. Use isto se o link de seus episódios privados foi vazado, pois isso irá escondê-los efetivamente.', 'clear_cache' => 'Limpar todo o cache', 'clear_cache_helper' => 'Esta opção irá liberar o cache do redis ou arquivos graváveis/cache.', 'run' => 'Executar manutenção', diff --git a/modules/Admin/Language/pt-BR/Soundbite.php b/modules/Admin/Language/pt-br/Soundbite.php similarity index 100% rename from modules/Admin/Language/pt-BR/Soundbite.php rename to modules/Admin/Language/pt-br/Soundbite.php diff --git a/modules/Admin/Language/pt-BR/Validation.php b/modules/Admin/Language/pt-br/Validation.php similarity index 77% rename from modules/Admin/Language/pt-BR/Validation.php rename to modules/Admin/Language/pt-br/Validation.php index 76ca2ae7..1db87454 100644 --- a/modules/Admin/Language/pt-BR/Validation.php +++ b/modules/Admin/Language/pt-br/Validation.php @@ -13,6 +13,5 @@ return [ '{field} não é uma imagem ou não é largo ou alto o suficiente.', 'is_image_ratio' => '{field} não é uma imagem ou não tem a proporção correta.', - 'validate_url' => - 'O campo {field} deve ser uma URL válida (por exemplo, https://examplo.com/).', + 'is_json' => '{field} contains invalid JSON.', ]; diff --git a/modules/Admin/Language/pt-BR/VideoClip.php b/modules/Admin/Language/pt-br/VideoClip.php similarity index 100% rename from modules/Admin/Language/pt-BR/VideoClip.php rename to modules/Admin/Language/pt-br/VideoClip.php diff --git a/modules/Admin/Language/pt/AboutCastopod.php b/modules/Admin/Language/pt/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/pt/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/pt/Breadcrumb.php b/modules/Admin/Language/pt/Breadcrumb.php index f3269bfa..408c9f9f 100644 --- a/modules/Admin/Language/pt/Breadcrumb.php +++ b/modules/Admin/Language/pt/Breadcrumb.php @@ -19,6 +19,7 @@ return [ 'pages' => 'pages', 'settings' => 'settings', 'theme' => 'theme', + 'about' => 'about', 'add' => 'add', 'new' => 'new', 'edit' => 'edit', @@ -28,15 +29,19 @@ return [ 'publish-date-edit' => 'edit publication date', 'unpublish' => 'unpublish', 'delete' => 'delete', + 'remove' => 'remove', 'fediverse' => 'fediverse', - 'block-lists' => 'block lists', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', 'users' => 'users', 'my-account' => 'my account', 'change-password' => 'change password', - 'import' => 'feed import', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', 'platforms' => 'platforms', 'social' => 'social networks', 'funding' => 'funding', + 'monetization-other' => 'other monetization', 'analytics' => 'analytics', 'locations' => 'locations', 'webpages' => 'web pages', diff --git a/modules/Admin/Language/pt/Charts.php b/modules/Admin/Language/pt/Charts.php index 4b33530e..6ede2510 100644 --- a/modules/Admin/Language/pt/Charts.php +++ b/modules/Admin/Language/pt/Charts.php @@ -37,4 +37,5 @@ return [ 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', 'total_storage_by_month' => 'Monthly storage (in MB)', 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', ]; diff --git a/modules/Admin/Language/pt/Common.php b/modules/Admin/Language/pt/Common.php index 596c8bcd..74addcf2 100644 --- a/modules/Admin/Language/pt/Common.php +++ b/modules/Admin/Language/pt/Common.php @@ -40,6 +40,7 @@ return [ ], 'upload_file' => 'Upload a file', 'remote_url' => 'Remote URL', + 'save' => 'Save', ], 'play_episode_button' => [ 'play' => 'Play', diff --git a/modules/Admin/Language/pt/Contributor.php b/modules/Admin/Language/pt/Contributor.php deleted file mode 100644 index d0f3b93d..00000000 --- a/modules/Admin/Language/pt/Contributor.php +++ /dev/null @@ -1,41 +0,0 @@ - 'Podcast contributors', - 'view' => "{username}'s contribution to {podcastTitle}", - 'add' => 'Add contributor', - 'add_contributor' => 'Add a contributor for {0}', - 'edit_role' => 'Update role for {0}', - 'edit' => 'Edit', - 'remove' => 'Remove', - 'list' => [ - 'username' => 'Username', - 'role' => 'Role', - ], - 'form' => [ - 'user' => 'User', - 'user_placeholder' => 'Select a user…', - 'role' => 'Role', - 'role_placeholder' => 'Select its role…', - 'submit_add' => 'Add contributor', - 'submit_edit' => 'Update role', - ], - 'roles' => [ - 'podcast_admin' => 'Podcast admin', - ], - 'messages' => [ - 'removeOwnerError' => "You can't remove the podcast owner!", - 'removeSuccess' => - 'You have successfully removed {username} from {podcastTitle}', - 'alreadyAddedError' => - "The contributor you're trying to add has already been added!", - ], -]; diff --git a/modules/Admin/Language/pt/Episode.php b/modules/Admin/Language/pt/Episode.php index 91313a7c..4fa846e3 100644 --- a/modules/Admin/Language/pt/Episode.php +++ b/modules/Admin/Language/pt/Episode.php @@ -22,6 +22,7 @@ return [ 'all_podcast_episodes' => 'All podcast episodes', 'back_to_podcast' => 'Go back to podcast', 'edit' => 'Edit', + 'preview' => 'Preview', 'publish' => 'Publish', 'publish_edit' => 'Edit publication', 'publish_date_edit' => 'Edit publication date', @@ -55,6 +56,7 @@ return [ }', 'episode' => 'Episode', 'visibility' => 'Visibility', + 'downloads' => 'Downloads', 'comments' => 'Comments', 'actions' => 'Actions', ], @@ -85,7 +87,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ @@ -137,9 +139,9 @@ return [ 'location_name' => 'Location name or address', 'location_name_hint' => 'This can be a real or fictional location', 'transcript' => 'Transcript (subtitles / closed captions)', - 'transcript_hint' => 'Only .srt are allowed.', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', 'transcript_download' => 'Download transcript', - 'transcript_file' => 'Transcript file (.srt)', + 'transcript_file' => 'Transcript file (.srt or .vtt)', 'transcript_remote_url' => 'Remote url for transcript', 'transcript_file_delete' => 'Delete transcript file', 'chapters' => 'Chapters', @@ -210,4 +212,14 @@ return [ 'light' => 'Light', 'light-transparent' => 'Light transparent', ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], ]; diff --git a/modules/Admin/Language/pt/Navigation.php b/modules/Admin/Language/pt/Navigation.php index 68d4609d..f3ffb129 100644 --- a/modules/Admin/Language/pt/Navigation.php +++ b/modules/Admin/Language/pt/Navigation.php @@ -12,12 +12,14 @@ return [ 'toggle_sidebar' => 'Toggle sidebar', 'go_to_website' => 'Go to website', 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', 'dashboard' => 'Dashboard', 'admin' => 'Home', 'podcasts' => 'Podcasts', 'podcast-list' => 'All podcasts', 'podcast-create' => 'New podcast', - 'podcast-import' => 'Import a podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', 'persons' => 'Persons', 'person-list' => 'All persons', 'person-create' => 'New person', @@ -33,6 +35,7 @@ return [ 'settings' => 'Settings', 'settings-general' => 'General', 'settings-theme' => 'Theme', + 'admin-about' => 'About', 'account' => [ 'my-account' => 'My account', 'change-password' => 'Change password', diff --git a/modules/Admin/Language/pt/Platforms.php b/modules/Admin/Language/pt/Platforms.php index ab17d599..e161181c 100644 --- a/modules/Admin/Language/pt/Platforms.php +++ b/modules/Admin/Language/pt/Platforms.php @@ -9,9 +9,22 @@ declare(strict_types=1); */ return [ - 'title' => 'Platforms', + 'title' => [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', 'visible' => 'Display in podcast homepage?', 'on_embed' => 'Display on embeddable player?', 'remove' => 'Remove {platformName}', diff --git a/modules/Admin/Language/pt/Podcast.php b/modules/Admin/Language/pt/Podcast.php index 426b763b..ff0daebc 100644 --- a/modules/Admin/Language/pt/Podcast.php +++ b/modules/Admin/Language/pt/Podcast.php @@ -13,6 +13,7 @@ return [ 'no_podcast' => 'No podcast found!', 'create' => 'Create podcast', 'import' => 'Import podcast', + 'all_imports' => 'Podcast imports', 'new_episode' => 'New Episode', 'view' => 'View podcast', 'edit' => 'Edit podcast', @@ -21,6 +22,7 @@ return [ 'delete' => 'Delete podcast', 'see_episodes' => 'See episodes', 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', 'go_to_page' => 'Go to page', 'latest_episodes' => 'Latest episodes', 'see_all_episodes' => 'See all episodes', @@ -48,7 +50,6 @@ return [ other {# episodes were} } added to the podcast!', 'podcastFeedUpToDate' => 'Podcast is already up to date.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', 'publishError' => 'This podcast is either already published or scheduled for publication.', 'publishEditError' => 'This podcast is not scheduled for publication.', 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', @@ -57,6 +58,8 @@ return [ 'form' => [ 'identity_section_title' => 'Podcast identity', 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + 'cover' => 'Podcast cover', 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', 'banner' => 'Podcast banner', @@ -71,7 +74,17 @@ return [ 'episodic' => 'Episodic', 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', 'serial' => 'Serial', - 'serial_hint' => 'If episodes are intended to be consumed in sequential order. The oldest episodes will be presented first.', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', ], 'description' => 'Description', 'classification_section_title' => 'Classification', @@ -96,6 +109,8 @@ return [ 'owner_email' => 'Owner email', 'owner_email_hint' => 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', 'publisher' => 'Publisher', 'publisher_hint' => 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', @@ -110,6 +125,11 @@ return [ 'premium' => 'Premium', 'premium_by_default' => 'Episodes must be set as premium by default', 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', 'payment_pointer' => 'Payment Pointer for Web Monetization', 'payment_pointer_hint' => 'This is your where you will receive money thanks to Web Monetization', @@ -118,11 +138,12 @@ return [ 'If you need RSS tags that Castopod does not handle, set them here.', 'custom_rss' => 'Custom RSS tags for the podcast', 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', 'new_feed_url' => 'New feed URL', 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', 'partnership' => 'Partnership', 'partner_id' => 'ID', 'partner_link_url' => 'Link URL', @@ -130,7 +151,6 @@ return [ 'partner_id_hint' => 'Your own partner ID', 'partner_link_url_hint' => 'The generic partner link address', 'partner_image_url_hint' => 'The generic partner image address', - 'status_section_title' => 'Status', 'block' => 'Podcast should be hidden from public catalogues', 'block_hint' => 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', diff --git a/modules/Admin/Language/pt/PodcastImport.php b/modules/Admin/Language/pt/PodcastImport.php deleted file mode 100644 index 7c3ef67d..00000000 --- a/modules/Admin/Language/pt/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'The podcast to import', - 'old_podcast_section_subtitle' => - 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', - 'imported_feed_url' => 'Feed URL', - 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', - 'new_podcast_section_title' => 'The new podcast', - 'advanced_params_section_title' => 'Advanced parameters', - 'advanced_params_section_subtitle' => - 'Keep the default values if you have no idea of what the fields are for.', - 'slug_field' => 'Field to be used to calculate episode slug', - 'description_field' => - 'Source field used for episode description / show notes', - 'force_renumber' => 'Force episodes renumbering', - 'force_renumber_hint' => - 'Use this if your podcast does not have episode numbers but wish to set them during import.', - 'season_number' => 'Season number', - 'season_number_hint' => - 'Use this if your podcast does not have a season number but wish to set one during import. Leave blank otherwise.', - 'max_episodes' => 'Maximum number of episodes to import', - 'max_episodes_hint' => 'Leave blank to import all episodes', - 'lock_import' => - 'This feed is protected. You cannot import it. If you are the owner, unprotect it on the origin platform.', - 'submit' => 'Import podcast', -]; diff --git a/modules/Admin/Language/pt/PodcastNavigation.php b/modules/Admin/Language/pt/PodcastNavigation.php index b4d7ddc0..bb777707 100644 --- a/modules/Admin/Language/pt/PodcastNavigation.php +++ b/modules/Admin/Language/pt/PodcastNavigation.php @@ -10,10 +10,13 @@ declare(strict_types=1); return [ 'go_to_page' => 'Go to podcast page', + 'rss_feed' => 'RSS feed', 'dashboard' => 'Podcast dashboard', 'podcast-view' => 'Home', 'podcast-edit' => 'Edit podcast', 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', 'episodes' => 'Episodes', 'episode-list' => 'All episodes', 'episode-create' => 'New episode', @@ -25,14 +28,15 @@ return [ 'podcast-analytics-players' => 'Players', 'podcast-analytics-listening-time' => 'Listening time', 'podcast-analytics-time-periods' => 'Time periods', - 'premium' => 'Premium', + 'monetization' => 'Monetization', 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', + 'subscription-create' => 'Add subscription', 'contributors' => 'Contributors', 'contributor-list' => 'All contributors', 'contributor-add' => 'Add contributor', - 'platforms' => 'External platforms', - 'platforms-podcasting' => 'Podcasting', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', 'platforms-social' => 'Social networks', - 'platforms-funding' => 'Funding', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', ]; diff --git a/modules/Admin/Language/pt/User.php b/modules/Admin/Language/pt/User.php deleted file mode 100644 index 585d6799..00000000 --- a/modules/Admin/Language/pt/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', - 'ban' => 'Ban', - 'unban' => 'Unban', - 'delete' => 'Delete', - 'create' => 'New user', - 'view' => "{username}'s info", - 'all_users' => 'All users', - 'list' => [ - 'user' => 'User', - 'roles' => 'Roles', - 'banned' => 'Banned?', - ], - 'form' => [ - 'email' => 'Email', - 'username' => 'Username', - 'password' => 'Password', - 'new_password' => 'New Password', - 'roles' => 'Roles', - 'permissions' => 'Permissions', - 'submit_create' => 'Create user', - 'submit_edit' => 'Save', - 'submit_password_change' => 'Change!', - ], - 'roles' => [ - 'superadmin' => 'Super admin', - ], - 'messages' => [ - 'createSuccess' => - 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => - "{username}'s roles have been successfully updated.", - 'forcePassResetSuccess' => - '{username} will be prompted with a password reset upon next visit.', - 'banSuccess' => '{username} has been banned.', - 'unbanSuccess' => '{username} has been unbanned.', - 'editOwnerError' => - '{username} is the instance owner, you cannot edit its roles.', - 'banSuperAdminError' => - '{username} is a superadmin, one does not simply ban a superadmin…', - 'deleteSuperAdminError' => - '{username} is a superadmin, one does not simply delete a superadmin…', - 'deleteSuccess' => '{username} has been deleted.', - ], -]; diff --git a/modules/Admin/Language/pt/Validation.php b/modules/Admin/Language/pt/Validation.php index 750b1968..f76c3163 100644 --- a/modules/Admin/Language/pt/Validation.php +++ b/modules/Admin/Language/pt/Validation.php @@ -13,6 +13,5 @@ return [ '{field} is either not an image, or it is not wide or tall enough.', 'is_image_ratio' => '{field} is either not an image or not of the right ratio.', - 'validate_url' => - 'The {field} field must be a valid URL (eg. https://example.com/).', + 'is_json' => '{field} contains invalid JSON.', ]; diff --git a/modules/Admin/Language/ro/AboutCastopod.php b/modules/Admin/Language/ro/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/ro/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/ro/Breadcrumb.php b/modules/Admin/Language/ro/Breadcrumb.php new file mode 100644 index 00000000..408c9f9f --- /dev/null +++ b/modules/Admin/Language/ro/Breadcrumb.php @@ -0,0 +1,57 @@ + 'breadcrumb', + config('Admin') + ->gateway => 'Home', + 'podcasts' => 'podcasts', + 'episodes' => 'episodes', + 'subscriptions' => 'subscriptions', + 'contributors' => 'contributors', + 'pages' => 'pages', + 'settings' => 'settings', + 'theme' => 'theme', + 'about' => 'about', + 'add' => 'add', + 'new' => 'new', + 'edit' => 'edit', + 'persons' => 'persons', + 'publish' => 'publish', + 'publish-edit' => 'edit publication', + 'publish-date-edit' => 'edit publication date', + 'unpublish' => 'unpublish', + 'delete' => 'delete', + 'remove' => 'remove', + 'fediverse' => 'fediverse', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', + 'users' => 'users', + 'my-account' => 'my account', + 'change-password' => 'change password', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', + 'platforms' => 'platforms', + 'social' => 'social networks', + 'funding' => 'funding', + 'monetization-other' => 'other monetization', + 'analytics' => 'analytics', + 'locations' => 'locations', + 'webpages' => 'web pages', + 'unique-listeners' => 'unique listeners', + 'players' => 'players', + 'listening-time' => 'listening time', + 'time-periods' => 'time periods', + 'soundbites' => 'soundbites', + 'video-clips' => 'video clips', + 'embed' => 'embeddable player', + 'notifications' => 'notifications', + 'suspend' => 'suspend', +]; diff --git a/modules/Admin/Language/ro/Charts.php b/modules/Admin/Language/ro/Charts.php new file mode 100644 index 00000000..6ede2510 --- /dev/null +++ b/modules/Admin/Language/ro/Charts.php @@ -0,0 +1,41 @@ + 'Episode downloads by service (for the past week)', + 'by_player_weekly' => 'Episode downloads by player (for the past week)', + 'by_player_yearly' => 'Episode downloads by player (for the past year)', + 'by_device_weekly' => 'Episode downloads by device (for the past week)', + 'by_os_weekly' => 'Episode downloads by O.S. (for the past week)', + 'podcast_by_region' => 'Episode downloads by region (for the past week)', + 'unique_daily_listeners' => 'Daily unique listeners', + 'unique_monthly_listeners' => 'Monthly unique listeners', + 'by_browser' => 'Web pages usage by browser (for the past week)', + 'podcast_by_day' => 'Episode daily downloads', + 'podcast_by_month' => 'Episode monthly downloads', + 'episode_by_day' => 'Episode daily downloads (first 60 days)', + 'episode_by_month' => 'Episode monthly downloads', + 'episodes_by_day' => + '5 latest episodes downloads (during their first 60 days)', + 'by_country_weekly' => 'Episode downloads by country (for the past week)', + 'by_country_yearly' => 'Episode downloads by country (for the past year)', + 'by_domain_weekly' => 'Web pages visits by source (for the past week)', + 'by_domain_yearly' => 'Web pages visits by source (for the past year)', + 'by_entry_page' => 'Web pages visits by landing page (for the past week)', + 'podcast_bots' => 'Bots (crawlers)', + 'daily_listening_time' => 'Daily cumulative listening time', + 'monthly_listening_time' => 'Monthly cumulative listening time', + 'by_weekday' => 'By week day (for the past 60 days)', + 'by_hour' => 'By time of day (for the past 60 days)', + 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', + 'total_storage_by_month' => 'Monthly storage (in MB)', + 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', +]; diff --git a/modules/Admin/Language/ro/Common.php b/modules/Admin/Language/ro/Common.php new file mode 100644 index 00000000..74addcf2 --- /dev/null +++ b/modules/Admin/Language/ro/Common.php @@ -0,0 +1,52 @@ + 'Yes', + 'no' => 'No', + 'cancel' => 'Cancel', + 'optional' => 'Optional', + 'more' => 'More', + 'no_data' => 'No data found!', + 'close' => 'Close', + 'edit' => 'Edit', + 'copy' => 'Copy', + 'copied' => 'Copied!', + 'home' => 'Home', + 'explicit' => 'Explicit', + 'powered_by' => 'Powered by {castopod}', + 'actions' => 'Actions', + 'pageInfo' => 'Page {currentPage} out of {pageCount}', + 'go_back' => 'Go back', + 'forms' => [ + 'editor' => [ + 'write' => 'Write', + 'preview' => 'Preview', + 'help' => 'Powered by markdown', + ], + 'multiSelect' => [ + 'selectText' => 'Press to select', + 'loadingText' => 'Loading…', + 'noResultsText' => 'No results found', + 'noChoicesText' => 'No choices to choose from', + 'maxItemText' => 'Cannot add more items', + ], + 'upload_file' => 'Upload a file', + 'remote_url' => 'Remote URL', + 'save' => 'Save', + ], + 'play_episode_button' => [ + 'play' => 'Play', + 'playing' => 'Playing', + ], + 'size_limit' => 'Size limit: {0}.', + 'choose_interact' => 'Choose how to interact', + 'view' => 'View', +]; diff --git a/modules/Admin/Language/ro/Countries.php b/modules/Admin/Language/ro/Countries.php new file mode 100644 index 00000000..4cd5d9c8 --- /dev/null +++ b/modules/Admin/Language/ro/Countries.php @@ -0,0 +1,264 @@ + 'Andorra', + 'AE' => 'United Arab Emirates', + 'AF' => 'Afghanistan', + 'AG' => 'Antigua and Barbuda', + 'AI' => 'Anguilla', + 'AL' => 'Albania', + 'AM' => 'Armenia', + 'AO' => 'Angola', + 'AQ' => 'Antarctica', + 'AR' => 'Argentina', + 'AS' => 'American Samoa', + 'AT' => 'Austria', + 'AU' => 'Australia', + 'AW' => 'Aruba', + 'AX' => 'Åland Islands', + 'AZ' => 'Azerbaijan', + 'BA' => 'Bosnia and Herzegovina', + 'BB' => 'Barbados', + 'BD' => 'Bangladesh', + 'BE' => 'Belgium', + 'BF' => 'Burkina Faso', + 'BG' => 'Bulgaria', + 'BH' => 'Bahrain', + 'BI' => 'Burundi', + 'BJ' => 'Benin', + 'BL' => 'Saint Barthélemy', + 'BM' => 'Bermuda', + 'BN' => 'Brunei Darussalam', + 'BO' => 'Bolivia, Plurinational State of', + 'BQ' => 'Bonaire, Sint Eustatius and Saba', + 'BR' => 'Brazil', + 'BS' => 'Bahamas', + 'BT' => 'Bhutan', + 'BV' => 'Bouvet Island', + 'BW' => 'Botswana', + 'BY' => 'Belarus', + 'BZ' => 'Belize', + 'CA' => 'Canada', + 'CC' => 'Cocos (Keeling) Islands', + 'CD' => 'Congo, the Democratic Republic of the', + 'CF' => 'Central African Republic', + 'CG' => 'Congo', + 'CH' => 'Switzerland', + 'CI' => "Côte d'Ivoire", + 'CK' => 'Cook Islands', + 'CL' => 'Chile', + 'CM' => 'Cameroon', + 'CN' => 'China', + 'CO' => 'Colombia', + 'CR' => 'Costa Rica', + 'CU' => 'Cuba', + 'CV' => 'Cape Verde', + 'CW' => 'Curaçao', + 'CX' => 'Christmas Island', + 'CY' => 'Cyprus', + 'CZ' => 'Czech Republic', + 'DE' => 'Germany', + 'DJ' => 'Djibouti', + 'DK' => 'Denmark', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'DZ' => 'Algeria', + 'EC' => 'Ecuador', + 'EE' => 'Estonia', + 'EG' => 'Egypt', + 'EH' => 'Western Sahara', + 'ER' => 'Eritrea', + 'ES' => 'Spain', + 'ET' => 'Ethiopia', + 'FI' => 'Finland', + 'FJ' => 'Fiji', + 'FK' => 'Falkland Islands (Malvinas)', + 'FM' => 'Micronesia, Federated States of', + 'FO' => 'Faroe Islands', + 'FR' => 'France', + 'GA' => 'Gabon', + 'GB' => 'United Kingdom', + 'GD' => 'Grenada', + 'GE' => 'Georgia', + 'GF' => 'French Guiana', + 'GG' => 'Guernsey', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GL' => 'Greenland', + 'GM' => 'Gambia', + 'GN' => 'Guinea', + 'GP' => 'Guadeloupe', + 'GQ' => 'Equatorial Guinea', + 'GR' => 'Greece', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'GT' => 'Guatemala', + 'GU' => 'Guam', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HK' => 'Hong Kong', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HR' => 'Croatia', + 'HT' => 'Haiti', + 'HU' => 'Hungary', + 'ID' => 'Indonesia', + 'IE' => 'Ireland', + 'IL' => 'Israel', + 'IM' => 'Isle of Man', + 'IN' => 'India', + 'IO' => 'British Indian Ocean Territory', + 'IQ' => 'Iraq', + 'IR' => 'Iran, Islamic Republic of', + 'IS' => 'Iceland', + 'IT' => 'Italy', + 'JE' => 'Jersey', + 'JM' => 'Jamaica', + 'JO' => 'Jordan', + 'JP' => 'Japan', + 'KE' => 'Kenya', + 'KG' => 'Kyrgyzstan', + 'KH' => 'Cambodia', + 'KI' => 'Kiribati', + 'KM' => 'Comoros', + 'KN' => 'Saint Kitts and Nevis', + 'KP' => "Korea, Democratic People's Republic of", + 'KR' => 'Korea, Republic of', + 'KW' => 'Kuwait', + 'KY' => 'Cayman Islands', + 'KZ' => 'Kazakhstan', + 'LA' => "Lao People's Democratic Republic", + 'LB' => 'Lebanon', + 'LC' => 'Saint Lucia', + 'LI' => 'Liechtenstein', + 'LK' => 'Sri Lanka', + 'LR' => 'Liberia', + 'LS' => 'Lesotho', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'LV' => 'Latvia', + 'LY' => 'Libya', + 'MA' => 'Morocco', + 'MC' => 'Monaco', + 'MD' => 'Moldova, Republic of', + 'ME' => 'Montenegro', + 'MF' => 'Saint Martin (French part)', + 'MG' => 'Madagascar', + 'MH' => 'Marshall Islands', + 'MK' => 'Macedonia, the Former Yugoslav Republic of', + 'ML' => 'Mali', + 'MM' => 'Myanmar', + 'MN' => 'Mongolia', + 'MO' => 'Macao', + 'MP' => 'Northern Mariana Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MS' => 'Montserrat', + 'MT' => 'Malta', + 'MU' => 'Mauritius', + 'MV' => 'Maldives', + 'MW' => 'Malawi', + 'MX' => 'Mexico', + 'MY' => 'Malaysia', + 'MZ' => 'Mozambique', + 'N/A' => 'Not Applicable (local IP…)', + 'NA' => 'Namibia', + 'NC' => 'New Caledonia', + 'NE' => 'Niger', + 'NF' => 'Norfolk Island', + 'NG' => 'Nigeria', + 'NI' => 'Nicaragua', + 'NL' => 'Netherlands', + 'NO' => 'Norway', + 'NP' => 'Nepal', + 'NR' => 'Nauru', + 'NU' => 'Niue', + 'NZ' => 'New Zealand', + 'OM' => 'Oman', + 'PA' => 'Panama', + 'PE' => 'Peru', + 'PF' => 'French Polynesia', + 'PG' => 'Papua New Guinea', + 'PH' => 'Philippines', + 'PK' => 'Pakistan', + 'PL' => 'Poland', + 'PM' => 'Saint Pierre and Miquelon', + 'PN' => 'Pitcairn', + 'PR' => 'Puerto Rico', + 'PS' => 'Palestine, State of', + 'PT' => 'Portugal', + 'PW' => 'Palau', + 'PY' => 'Paraguay', + 'QA' => 'Qatar', + 'RE' => 'Réunion', + 'RO' => 'Romania', + 'RS' => 'Serbia', + 'RU' => 'Russian Federation', + 'RW' => 'Rwanda', + 'SA' => 'Saudi Arabia', + 'SB' => 'Solomon Islands', + 'SC' => 'Seychelles', + 'SD' => 'Sudan', + 'SE' => 'Sweden', + 'SG' => 'Singapore', + 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', + 'SI' => 'Slovenia', + 'SJ' => 'Svalbard and Jan Mayen', + 'SK' => 'Slovakia', + 'SL' => 'Sierra Leone', + 'SM' => 'San Marino', + 'SN' => 'Senegal', + 'SO' => 'Somalia', + 'SR' => 'Suriname', + 'SS' => 'South Sudan', + 'ST' => 'Sao Tome and Principe', + 'SV' => 'El Salvador', + 'SX' => 'Sint Maarten (Dutch part)', + 'SY' => 'Syrian Arab Republic', + 'SZ' => 'Swaziland', + 'TC' => 'Turks and Caicos Islands', + 'TD' => 'Chad', + 'TF' => 'French Southern Territories', + 'TG' => 'Togo', + 'TH' => 'Thailand', + 'TJ' => 'Tajikistan', + 'TK' => 'Tokelau', + 'TL' => 'Timor-Leste', + 'TM' => 'Turkmenistan', + 'TN' => 'Tunisia', + 'TO' => 'Tonga', + 'TR' => 'Turkey', + 'TT' => 'Trinidad and Tobago', + 'TV' => 'Tuvalu', + 'TW' => 'Taiwan, Province of China', + 'TZ' => 'Tanzania, United Republic of', + 'UA' => 'Ukraine', + 'UG' => 'Uganda', + 'UM' => 'United States Minor Outlying Islands', + 'US' => 'United States', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VA' => 'Holy See (Vatican City State)', + 'VC' => 'Saint Vincent and the Grenadines', + 'VE' => 'Venezuela, Bolivarian Republic of', + 'VG' => 'Virgin Islands, British', + 'VI' => 'Virgin Islands, U.S.', + 'VN' => 'Viet Nam', + 'VU' => 'Vanuatu', + 'WF' => 'Wallis and Futuna', + 'WS' => 'Samoa', + 'YE' => 'Yemen', + 'YT' => 'Mayotte', + 'ZA' => 'South Africa', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', +]; diff --git a/modules/Admin/Language/ro/Dashboard.php b/modules/Admin/Language/ro/Dashboard.php new file mode 100644 index 00000000..881073fd --- /dev/null +++ b/modules/Admin/Language/ro/Dashboard.php @@ -0,0 +1,28 @@ + 'Admin dashboard', + 'welcome_message' => 'Welcome to the admin area!', + 'podcasts' => [ + 'title' => 'Podcasts', + 'not_found' => 'No published podcast', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'episodes' => [ + 'title' => 'Episodes', + 'not_found' => 'No published episode', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'storage' => [ + 'title' => 'Storage', + 'subtitle' => '{totalUploaded} out of {totalStorage}', + ], +]; diff --git a/modules/Admin/Language/ro/Episode.php b/modules/Admin/Language/ro/Episode.php new file mode 100644 index 00000000..4fa846e3 --- /dev/null +++ b/modules/Admin/Language/ro/Episode.php @@ -0,0 +1,225 @@ + 'Season {seasonNumber}', + 'season_abbr' => 'S{seasonNumber}', + 'number' => 'Episode {episodeNumber}', + 'number_abbr' => 'Ep. {episodeNumber}', + 'season_episode' => 'Season {seasonNumber} episode {episodeNumber}', + 'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}', + 'number_of_comments' => '{numberOfComments, plural, + one {# comment} + other {# comments} + }', + 'all_podcast_episodes' => 'All podcast episodes', + 'back_to_podcast' => 'Go back to podcast', + 'edit' => 'Edit', + 'preview' => 'Preview', + 'publish' => 'Publish', + 'publish_edit' => 'Edit publication', + 'publish_date_edit' => 'Edit publication date', + 'unpublish' => 'Unpublish', + 'publish_error' => 'Episode is already published.', + 'publish_edit_error' => 'Episode is already published.', + 'publish_cancel_error' => 'Episode is already published.', + 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', + 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', + 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'unpublish_error' => 'Episode is not published.', + 'delete' => 'Delete', + 'go_to_page' => 'Go to page', + 'create' => 'Add an episode', + 'publication_status' => [ + 'published' => 'Published', + 'with_podcast' => 'Published', + 'scheduled' => 'Scheduled', + 'not_published' => 'Not published', + ], + 'with_podcast_hint' => 'To be published at the same time as the podcast', + 'list' => [ + 'search' => [ + 'placeholder' => 'Search for an episode', + 'clear' => 'Clear search', + 'submit' => 'Search', + ], + 'number_of_episodes' => '{numberOfEpisodes, plural, + one {# episode} + other {# episodes} + }', + 'episode' => 'Episode', + 'visibility' => 'Visibility', + 'downloads' => 'Downloads', + 'comments' => 'Comments', + 'actions' => 'Actions', + ], + 'messages' => [ + 'createSuccess' => 'Episode has been successfully created!', + 'editSuccess' => 'Episode has been successfully updated!', + 'publishSuccess' => '{publication_status, select, + published {Episode successfully published!} + scheduled {Episode publication successfully scheduled!} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not published.} + }', + 'publishCancelSuccess' => 'Episode publication successfully cancelled!', + 'unpublishBeforeDeleteTip' => 'You must unpublish the episode before deleting it.', + 'scheduleDateError' => 'Schedule date must be set!', + 'deletePublishedEpisodeError' => 'Please unpublish the episode before deleting it.', + 'deleteSuccess' => 'Episode successfully deleted!', + 'deleteError' => 'Failed to delete episode {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deleteFileError' => 'Failed to delete {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + } file {file_key}. You may manually remove it from your disk.', + 'sameSlugError' => 'An episode with the chosen slug already exists.', + ], + 'form' => [ + 'file_size_error' => + 'Your file size is too big! Max size is {0}. Increase the `memory_limit`, `upload_max_filesize` and `post_max_size` values in your php configuration file then restart your web server to upload your file.', + 'audio_file' => 'Audio file', + 'audio_file_hint' => 'Choose an .mp3 or .m4a audio file.', + 'info_section_title' => 'Episode info', + 'cover' => 'Episode cover', + 'cover_hint' => + 'If you do not set a cover, the podcast cover will be used instead.', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'title' => 'Title', + 'title_hint' => + 'Should contain a clear and concise episode name. Do not specify the episode or season numbers here.', + 'permalink' => 'Permalink', + 'season_number' => 'Season', + 'episode_number' => 'Episode', + 'type' => [ + 'label' => 'Type', + 'full' => 'Full', + 'full_hint' => 'Complete content (the episode)', + 'trailer' => 'Trailer', + 'trailer_hint' => 'Short, promotional piece of content that represents a preview of the current show', + 'bonus' => 'Bonus', + 'bonus_hint' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show', + ], + 'premium_title' => 'Premium', + 'premium' => 'Episode must be accessible to premium subscribers only', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does the episode contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'show_notes_section_title' => 'Show notes', + 'show_notes_section_subtitle' => + 'Up to 4000 characters, be clear and concise. Show notes help potential listeners in finding the episode.', + 'description' => 'Description', + 'description_footer' => 'Description footer', + 'description_footer_hint' => + 'This text is added at the end of each episode description, it is a good place to input your social links for example.', + 'additional_files_section_title' => 'Additional files', + 'additional_files_section_subtitle' => + 'These files may be used by other platforms to provide better experience to your audience. See the {podcastNamespaceLink} for more information.', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this episode about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real or fictional location', + 'transcript' => 'Transcript (subtitles / closed captions)', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', + 'transcript_download' => 'Download transcript', + 'transcript_file' => 'Transcript file (.srt or .vtt)', + 'transcript_remote_url' => 'Remote url for transcript', + 'transcript_file_delete' => 'Delete transcript file', + 'chapters' => 'Chapters', + 'chapters_hint' => 'File must be in JSON Chapters format.', + 'chapters_download' => 'Download chapters', + 'chapters_file' => 'Chapters file', + 'chapters_remote_url' => 'Remote url for chapters file', + 'chapters_file_delete' => 'Delete chapters file', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the episode', + 'custom_rss_hint' => 'This will be injected within the ❬item❭ tag.', + 'block' => 'Episode should be hidden from public catalogues', + 'block_hint' => + 'The episode show or hide status: toggling this on prevents the episode from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'submit_create' => 'Create episode', + 'submit_edit' => 'Save episode', + ], + 'publish_form' => [ + 'back_to_episode_dashboard' => 'Back to episode dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your episode. The message will be broadcasted to all your followers in the fediverse and be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + 'with_podcast' => 'Publish alongside podcast', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_clear' => 'Clear publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the episode release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit' => 'Publish', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your episode.', + 'message_warning_submit' => 'Publish anyways', + ], + 'publish_date_edit_form' => [ + 'new_publication_date' => 'New publication date', + 'new_publication_date_hint' => 'Must be set to a past date.', + 'submit' => 'Edit publication date', + ], + 'unpublish_form' => [ + 'disclaimer' => + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", + 'understand' => 'I understand, I want to unpublish the episode', + 'submit' => 'Unpublish', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.", + 'understand' => 'I understand, I want to delete the episode', + 'submit' => 'Delete', + ], + 'embed' => [ + 'title' => 'Embeddable player', + 'label' => + 'Pick a theme color, copy the embeddable player to clipboard, then paste it on your website.', + 'clipboard_iframe' => 'Copy embeddable player to clipboard', + 'clipboard_url' => 'Copy address to clipboard', + 'dark' => 'Dark', + 'dark-transparent' => 'Dark transparent', + 'light' => 'Light', + 'light-transparent' => 'Light transparent', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], +]; diff --git a/modules/Admin/Language/ro/EpisodeNavigation.php b/modules/Admin/Language/ro/EpisodeNavigation.php new file mode 100644 index 00000000..1406e301 --- /dev/null +++ b/modules/Admin/Language/ro/EpisodeNavigation.php @@ -0,0 +1,23 @@ + 'View episode page', + 'dashboard' => 'Episode dashboard', + 'episode-view' => 'Home', + 'episode-edit' => 'Edit episode', + 'episode-persons-manage' => 'Manage persons', + 'embed-add' => 'Embeddable player', + 'clips' => 'Clips', + 'video-clips-list' => 'Video clips', + 'video-clips-create' => 'New video clip', + 'soundbites-list' => 'Soundbites', + 'soundbites-create' => 'New soundbite', +]; diff --git a/modules/Admin/Language/ro/Fediverse.php b/modules/Admin/Language/ro/Fediverse.php new file mode 100644 index 00000000..0e4ca66d --- /dev/null +++ b/modules/Admin/Language/ro/Fediverse.php @@ -0,0 +1,32 @@ + [ + 'actorNotFound' => 'The account could not be found!', + 'blockActorSuccess' => '{actor} has been blocked!', + 'unblockActorSuccess' => 'Actor has been unblocked!', + 'blockDomainSuccess' => '{domain} has been blocked!', + 'unblockDomainSuccess' => '{domain} has been unblocked!', + ], + 'blocked_actors' => 'Blocked accounts', + 'blocked_domains' => 'Blocked domains', + 'block_lists_form' => [ + 'handle' => 'Account handle', + 'handle_hint' => 'Input @username@domain account.', + 'domain' => 'Domain name', + 'submit' => 'Block!', + ], + 'list' => [ + 'actor' => 'Account', + 'domain' => 'Domain name', + 'unblock' => 'Unblock', + ], +]; diff --git a/modules/Admin/Language/ro/Home.php b/modules/Admin/Language/ro/Home.php new file mode 100644 index 00000000..3ff4c04d --- /dev/null +++ b/modules/Admin/Language/ro/Home.php @@ -0,0 +1,14 @@ + 'All podcasts', + 'no_podcast' => 'No podcast found', +]; diff --git a/modules/Admin/Language/ro/Install.php b/modules/Admin/Language/ro/Install.php new file mode 100644 index 00000000..36e373a2 --- /dev/null +++ b/modules/Admin/Language/ro/Install.php @@ -0,0 +1,61 @@ + 'Manual configuration', + 'manual_config_subtitle' => + 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'form' => [ + 'instance_config' => 'Instance configuration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', + 'db_hostname' => 'Database hostname', + 'db_name' => 'Database name', + 'db_username' => 'Database username', + 'db_password' => 'Database password', + 'db_prefix' => 'Database prefix', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Cache handler', + 'cacheHandlerOptions' => [ + 'file' => 'File', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Next', + 'submit' => 'Finish install', + 'create_superadmin' => 'Create your superadmin account', + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Your superadmin account has been created successfully. Login to start podcasting!', + 'databaseConnectError' => + 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'writeError' => + "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + ], +]; diff --git a/modules/Admin/Language/ro/Navigation.php b/modules/Admin/Language/ro/Navigation.php new file mode 100644 index 00000000..f3ffb129 --- /dev/null +++ b/modules/Admin/Language/ro/Navigation.php @@ -0,0 +1,44 @@ + 'Toggle sidebar', + 'go_to_website' => 'Go to website', + 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', + 'dashboard' => 'Dashboard', + 'admin' => 'Home', + 'podcasts' => 'Podcasts', + 'podcast-list' => 'All podcasts', + 'podcast-create' => 'New podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', + 'persons' => 'Persons', + 'person-list' => 'All persons', + 'person-create' => 'New person', + 'fediverse' => 'Fediverse', + 'fediverse-blocked-actors' => 'Blocked accounts', + 'fediverse-blocked-domains' => 'Blocked domains', + 'users' => 'Users', + 'user-list' => 'All users', + 'user-create' => 'New user', + 'pages' => 'Pages', + 'page-list' => 'All pages', + 'page-create' => 'New Page', + 'settings' => 'Settings', + 'settings-general' => 'General', + 'settings-theme' => 'Theme', + 'admin-about' => 'About', + 'account' => [ + 'my-account' => 'My account', + 'change-password' => 'Change password', + 'logout' => 'Logout', + ], +]; diff --git a/modules/Admin/Language/ro/Notifications.php b/modules/Admin/Language/ro/Notifications.php new file mode 100644 index 00000000..2b139d51 --- /dev/null +++ b/modules/Admin/Language/ro/Notifications.php @@ -0,0 +1,19 @@ + 'Notifications', + 'reply' => '{actor_username} replied to your post', + 'favourite' => '{actor_username} favourited your post', + 'reblog' => '{actor_username} shared your post', + 'follow' => '{actor_username} started following you', + 'no_notifications' => 'No notifications', + 'mark_all_as_read' => 'Mark all as read', +]; diff --git a/modules/Admin/Language/ro/Page.php b/modules/Admin/Language/ro/Page.php new file mode 100644 index 00000000..b6f49de5 --- /dev/null +++ b/modules/Admin/Language/ro/Page.php @@ -0,0 +1,30 @@ + 'Back to home', + 'page' => 'Page', + 'all_pages' => 'All pages', + 'create' => 'New page', + 'go_to_page' => 'Go to page', + 'edit' => 'Edit page', + 'delete' => 'Delete page', + 'form' => [ + 'title' => 'Title', + 'permalink' => 'Permalink', + 'content' => 'Content', + 'submit_create' => 'Create page', + 'submit_edit' => 'Save', + ], + 'messages' => [ + 'createSuccess' => 'The page “{pageTitle}” was created successfully!', + 'editSuccess' => 'The page was successfully updated!', + ], +]; diff --git a/modules/Admin/Language/ro/Pager.php b/modules/Admin/Language/ro/Pager.php new file mode 100644 index 00000000..e25ee638 --- /dev/null +++ b/modules/Admin/Language/ro/Pager.php @@ -0,0 +1,21 @@ + 'Page navigation', + 'first' => 'First', + 'previous' => 'Previous', + 'next' => 'Next', + 'last' => 'Last', + 'older' => 'Older', + 'newer' => 'Newer', + 'invalidTemplate' => '{0} is not a valid Pager template.', + 'invalidPaginationGroup' => '{0} is not a valid Pagination group.', +]; diff --git a/modules/Admin/Language/ro/Person.php b/modules/Admin/Language/ro/Person.php new file mode 100644 index 00000000..a652be9f --- /dev/null +++ b/modules/Admin/Language/ro/Person.php @@ -0,0 +1,65 @@ + 'Persons', + 'all_persons' => 'All persons', + 'no_person' => 'Nobody found!', + 'create' => 'Create a person', + 'view' => 'View person', + 'edit' => 'Edit person', + 'delete' => 'Delete person', + 'messages' => [ + 'createSuccess' => 'Person has been successfully created!', + 'editSuccess' => 'Person has been successfully updated!', + 'deleteSuccess' => 'Person has been removed!', + ], + 'form' => [ + 'avatar' => 'Avatar', + 'avatar_size_hint' => + 'Avatar must be squared and at least 400px wide and tall.', + 'full_name' => 'Full name', + 'full_name_hint' => 'This is the full name or alias of the person.', + 'unique_name' => 'Unique name', + 'unique_name_hint' => 'Used for URLs', + 'information_url' => 'Information URL', + 'information_url_hint' => + 'Url to a relevant resource of information about the person, such as a homepage or third-party profile platform.', + 'submit_create' => 'Create person', + 'submit_edit' => 'Save person', + ], + 'podcast_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this podcast', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'episode_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this episode', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'credits' => 'Credits', +]; diff --git a/modules/Admin/Language/ro/Platforms.php b/modules/Admin/Language/ro/Platforms.php new file mode 100644 index 00000000..e161181c --- /dev/null +++ b/modules/Admin/Language/ro/Platforms.php @@ -0,0 +1,43 @@ + [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', + 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', + 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', + 'visible' => 'Display in podcast homepage?', + 'on_embed' => 'Display on embeddable player?', + 'remove' => 'Remove {platformName}', + 'submit' => 'Save', + 'messages' => [ + 'updateSuccess' => 'Platform links have been successfully updated!', + 'removeLinkSuccess' => 'The platform link has been removed.', + 'removeLinkError' => + 'The platform link could not be removed. Try again.', + ], + 'description' => [ + 'podcasting' => 'The podcast ID on this platform', + 'social' => 'The podcast account ID on this platform', + 'funding' => 'Call to action message', + ], +]; diff --git a/modules/Admin/Language/ro/Podcast.php b/modules/Admin/Language/ro/Podcast.php new file mode 100644 index 00000000..ff0daebc --- /dev/null +++ b/modules/Admin/Language/ro/Podcast.php @@ -0,0 +1,330 @@ + 'All podcasts', + 'no_podcast' => 'No podcast found!', + 'create' => 'Create podcast', + 'import' => 'Import podcast', + 'all_imports' => 'Podcast imports', + 'new_episode' => 'New Episode', + 'view' => 'View podcast', + 'edit' => 'Edit podcast', + 'publish' => 'Publish podcast', + 'publish_edit' => 'Edit publication', + 'delete' => 'Delete podcast', + 'see_episodes' => 'See episodes', + 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', + 'go_to_page' => 'Go to page', + 'latest_episodes' => 'Latest episodes', + 'see_all_episodes' => 'See all episodes', + 'draft' => 'Draft', + 'messages' => [ + 'createSuccess' => 'Podcast successfully created!', + 'editSuccess' => 'Podcast has been successfully updated!', + 'importSuccess' => 'Podcast has been successfully imported!', + 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', + 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, + cover {cover} + banner {banner} + other {media} + }.', + 'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.', + 'podcastFeedUpdateSuccess' => 'Successful update: {number_of_new_episodes, plural, + one {# episode was} + other {# episodes were} + } added to the podcast!', + 'podcastFeedUpToDate' => 'Podcast is already up to date.', + 'publishError' => 'This podcast is either already published or scheduled for publication.', + 'publishEditError' => 'This podcast is not scheduled for publication.', + 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', + 'scheduleDateError' => 'Schedule date must be set!', + ], + 'form' => [ + 'identity_section_title' => 'Podcast identity', + 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + + 'cover' => 'Podcast cover', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'banner' => 'Podcast banner', + 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', + 'banner_delete' => 'Delete podcast banner', + 'title' => 'Title', + 'handle' => 'Handle', + 'handle_hint' => + 'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.', + 'type' => [ + 'label' => 'Type', + 'episodic' => 'Episodic', + 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', + 'serial' => 'Serial', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + ], + 'description' => 'Description', + 'classification_section_title' => 'Classification', + 'classification_section_subtitle' => + 'These fields will impact your audience and competition.', + 'language' => 'Language', + 'category' => 'Category', + 'category_placeholder' => 'Select a category…', + 'other_categories' => 'Other categories', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does it contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'author_section_title' => 'Author', + 'author_section_subtitle' => 'Who is managing the podcast?', + 'owner_name' => 'Owner name', + 'owner_name_hint' => + 'For administrative use only. Visible in the public RSS feed.', + 'owner_email' => 'Owner email', + 'owner_email_hint' => + 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', + 'publisher' => 'Publisher', + 'publisher_hint' => + 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', + 'copyright' => 'Copyright', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this podcast about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real place or fictional', + 'monetization_section_title' => 'Monetization', + 'monetization_section_subtitle' => + 'Earn money thanks to your audience.', + 'premium' => 'Premium', + 'premium_by_default' => 'Episodes must be set as premium by default', + 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', + 'payment_pointer' => 'Payment Pointer for Web Monetization', + 'payment_pointer_hint' => + 'This is your where you will receive money thanks to Web Monetization', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the podcast', + 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', + 'new_feed_url' => 'New feed URL', + 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', + 'old_feed_url' => 'Old feed URL', + 'partnership' => 'Partnership', + 'partner_id' => 'ID', + 'partner_link_url' => 'Link URL', + 'partner_image_url' => 'Image URL', + 'partner_id_hint' => 'Your own partner ID', + 'partner_link_url_hint' => 'The generic partner link address', + 'partner_image_url_hint' => 'The generic partner image address', + 'block' => 'Podcast should be hidden from public catalogues', + 'block_hint' => + 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'complete' => 'Podcast will not be having new episodes', + 'lock' => 'Prevent podcast from being copied', + 'lock_hint' => + 'The purpose is to tell other podcast platforms whether they are allowed to import this feed. A value of yes means that any attempt to import this feed into a new platform should be rejected.', + 'submit_create' => 'Create podcast', + 'submit_edit' => 'Save podcast', + ], + 'category_options' => [ + 'uncategorized' => 'uncategorized', + 'arts' => 'Arts', + 'business' => 'Business', + 'comedy' => 'Comedy', + 'education' => 'Education', + 'fiction' => 'Fiction', + 'government' => 'Government', + 'health_and_fitness' => 'Health & Fitness', + 'history' => 'History', + 'kids_and_family' => 'Kids & Family', + 'leisure' => 'Leisure', + 'music' => 'Music', + 'news' => 'News', + 'religion_and_spirituality' => 'Religion & Spirituality', + 'science' => 'Science', + 'society_and_culture' => 'Society & Culture', + 'sports' => 'Sports', + 'technology' => 'Technology', + 'true_crime' => 'True Crime', + 'tv_and_film' => 'TV & Film', + 'books' => 'Books', + 'design' => 'Design', + 'fashion_and_beauty' => 'Fashion & Beauty', + 'food' => 'Food', + 'performing_arts' => 'Performing Arts', + 'visual_arts' => 'Visual Arts', + 'careers' => 'Careers', + 'entrepreneurship' => 'Entrepreneurship', + 'investing' => 'Investing', + 'management' => 'Management', + 'marketing' => 'Marketing', + 'non_profit' => 'Non-Profit', + 'comedy_interviews' => 'Comedy Interviews', + 'improv' => 'Improv', + 'stand_up' => 'Stand-Up', + 'courses' => 'Courses', + 'how_to' => 'How To', + 'language_learning' => 'Language Learning', + 'self_improvement' => 'Self-Improvement', + 'comedy_fiction' => 'Comedy Fiction', + 'drama' => 'Drama', + 'science_fiction' => 'Science Fiction', + 'alternative_health' => 'Alternative Health', + 'fitness' => 'Fitness', + 'medicine' => 'Medicine', + 'mental_health' => 'Mental Health', + 'nutrition' => 'Nutrition', + 'sexuality' => 'Sexuality', + 'education_for_kids' => 'Education for Kids', + 'parenting' => 'Parenting', + 'pets_and_animals' => 'Pets & Animals', + 'stories_for_kids' => 'Stories for Kids', + 'animation_and_manga' => 'Animation & Manga', + 'automotive' => 'Automotive', + 'aviation' => 'Aviation', + 'crafts' => 'Crafts', + 'games' => 'Games', + 'hobbies' => 'Hobbies', + 'home_and_garden' => 'Home & Garden', + 'video_games' => 'Video Games', + 'music_commentary' => 'Music Commentary', + 'music_history' => 'Music History', + 'music_interviews' => 'Music Interviews', + 'business_news' => 'Business News', + 'daily_news' => 'Daily News', + 'entertainment_news' => 'Entertainment News', + 'news_commentary' => 'News Commentary', + 'politics' => 'Politics', + 'sports_news' => 'Sports News', + 'tech_news' => 'Tech News', + 'buddhism' => 'Buddhism', + 'christianity' => 'Christianity', + 'hinduism' => 'Hinduism', + 'islam' => 'Islam', + 'judaism' => 'Judaism', + 'religion' => 'Religion', + 'spirituality' => 'Spirituality', + 'astronomy' => 'Astronomy', + 'chemistry' => 'Chemistry', + 'earth_sciences' => 'Earth Sciences', + 'life_sciences' => 'Life Sciences', + 'mathematics' => 'Mathematics', + 'natural_sciences' => 'Natural Sciences', + 'nature' => 'Nature', + 'physics' => 'Physics', + 'social_sciences' => 'Social Sciences', + 'documentary' => 'Documentary', + 'personal_journals' => 'Personal Journals', + 'philosophy' => 'Philosophy', + 'places_and_travel' => 'Places & Travel', + 'relationships' => 'Relationships', + 'baseball' => 'Baseball', + 'basketball' => 'Basketball', + 'cricket' => 'Cricket', + 'fantasy_sports' => 'Fantasy Sports', + 'football' => 'Football', + 'golf' => 'Golf', + 'hockey' => 'Hockey', + 'rugby' => 'Rugby', + 'running' => 'Running', + 'soccer' => 'Soccer', + 'swimming' => 'Swimming', + 'tennis' => 'Tennis', + 'volleyball' => 'Volleyball', + 'wilderness' => 'Wilderness', + 'wrestling' => 'Wrestling', + 'after_shows' => 'After Shows', + 'film_history' => 'Film History', + 'film_interviews' => 'Film Interviews', + 'film_reviews' => 'Film Reviews', + 'tv_reviews' => 'TV Reviews', + ], + 'publish_form' => [ + 'back_to_podcast_dashboard' => 'Back to podcast dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'submit' => 'Publish', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', + 'message_warning_submit' => 'Publish anyway', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'not_published' => 'This podcast is not yet published.', + 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", + 'understand' => 'I understand, I want the podcast to be permanently deleted', + 'submit' => 'Delete', + ], + 'by' => 'By {publisher}', + 'season' => 'Season {seasonNumber}', + 'list_of_episodes_year' => '{year} episodes ({episodeCount})', + 'list_of_episodes_season' => + 'Season {seasonNumber} episodes ({episodeCount})', + 'no_episode' => 'No episode found!', + 'follow' => 'Follow', + 'followers' => '{numberOfFollowers, plural, + one {# follower} + other {# followers} + }', + 'posts' => '{numberOfPosts, plural, + one {# post} + other {# posts} + }', + 'activity' => 'Activity', + 'episodes' => 'Episodes', + 'sponsor' => 'Sponsor', + 'funding_links' => 'Funding links for {podcastTitle}', + 'find_on' => 'Find {podcastTitle} on', + 'listen_on' => 'Listen on', +]; diff --git a/modules/Admin/Language/ro/PodcastNavigation.php b/modules/Admin/Language/ro/PodcastNavigation.php new file mode 100644 index 00000000..bb777707 --- /dev/null +++ b/modules/Admin/Language/ro/PodcastNavigation.php @@ -0,0 +1,42 @@ + 'Go to podcast page', + 'rss_feed' => 'RSS feed', + 'dashboard' => 'Podcast dashboard', + 'podcast-view' => 'Home', + 'podcast-edit' => 'Edit podcast', + 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', + 'episodes' => 'Episodes', + 'episode-list' => 'All episodes', + 'episode-create' => 'New episode', + 'analytics' => 'Analytics', + 'podcast-analytics' => 'Audience overview', + 'podcast-analytics-webpages' => 'Web pages visits', + 'podcast-analytics-locations' => 'Locations', + 'podcast-analytics-unique-listeners' => 'Unique listeners', + 'podcast-analytics-players' => 'Players', + 'podcast-analytics-listening-time' => 'Listening time', + 'podcast-analytics-time-periods' => 'Time periods', + 'monetization' => 'Monetization', + 'subscription-list' => 'All subscriptions', + 'subscription-create' => 'Add subscription', + 'contributors' => 'Contributors', + 'contributor-list' => 'All contributors', + 'contributor-add' => 'Add contributor', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', + 'platforms-social' => 'Social networks', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', +]; diff --git a/modules/Admin/Language/ro/Settings.php b/modules/Admin/Language/ro/Settings.php new file mode 100644 index 00000000..4a70dcba --- /dev/null +++ b/modules/Admin/Language/ro/Settings.php @@ -0,0 +1,58 @@ + 'General settings', + 'instance' => [ + 'title' => 'Instance', + 'site_icon' => 'Site icon', + 'site_icon_delete' => 'Delete site icon', + 'site_icon_hint' => 'Site icons are what you see on your browser tabs, bookmarks bar, and when you add a website as a shortcut on mobile devices.', + 'site_icon_helper' => 'Icon must be squared and at least 512px wide and tall.', + 'site_name' => 'Site name', + 'site_description' => 'Site description', + 'submit' => 'Save', + 'editSuccess' => 'Instance has been updated successfully!', + 'deleteIconSuccess' => 'Site icon has been remove successfully!', + ], + 'images' => [ + 'title' => 'Images', + 'subtitle' => 'Here you can regenerate all images based on the originals that were uploaded. To be used if you find that some images are missing. This task may take a while.', + 'regenerate' => 'Regenerate images', + 'regenerationSuccess' => 'All images have been regenerated successfully!', + ], + 'housekeeping' => [ + 'title' => 'Housekeeping', + 'subtitle' => 'Runs various housekeeping tasks. Use this feature if you ever encounter issues with media files or data integrity. These tasks may take a while.', + 'reset_counts' => 'Reset counts', + 'reset_counts_helper' => 'This option will recalculate and reset all data counts (number of followers, posts, comments, …).', + 'rewrite_media' => 'Rewrite media metadata', + 'rewrite_media_helper' => 'This option will delete all superfluous media files and recreate them (images, audio files, transcripts, chapters, …)', + 'rename_episodes_files' => 'Rename episode audio files', + 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', + 'clear_cache' => 'Clear all cache', + 'clear_cache_helper' => 'This option will flush redis cache or writable/cache files.', + 'run' => 'Run housekeeping', + 'runSuccess' => 'Housekeeping has been run successfully!', + ], + 'theme' => [ + 'title' => 'Theme', + 'accent_section_title' => 'Accent color', + 'accent_section_subtitle' => 'Choose the color to determine the look and feel of all public pages.', + 'pine' => 'Pine', + 'crimson' => 'Crimson', + 'amber' => 'Amber', + 'lake' => 'Lake', + 'jacaranda' => 'Jacaranda', + 'onyx' => 'Onyx', + 'submit' => 'Save', + 'setInstanceThemeSuccess' => 'Theme has been updated successfully!', + ], +]; diff --git a/modules/Admin/Language/ro/Soundbite.php b/modules/Admin/Language/ro/Soundbite.php new file mode 100644 index 00000000..a3f828fe --- /dev/null +++ b/modules/Admin/Language/ro/Soundbite.php @@ -0,0 +1,31 @@ + [ + 'title' => 'Soundbites', + 'soundbite' => 'Soundbite', + ], + 'messages' => [ + 'createSuccess' => 'Soundbite has been successfully created!', + 'deleteSuccess' => 'Soundbite has been successfully removed!', + ], + 'form' => [ + 'title' => 'New soundbite', + 'soundbite_title' => 'Soundbite title', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'submit' => 'Create soundbite', + ], + 'play' => 'Play soundbite', + 'stop' => 'Stop soundbite', + 'create' => 'New soundbite', + 'delete' => 'Delete soundbite', +]; diff --git a/modules/Admin/Language/ro/Validation.php b/modules/Admin/Language/ro/Validation.php new file mode 100644 index 00000000..f76c3163 --- /dev/null +++ b/modules/Admin/Language/ro/Validation.php @@ -0,0 +1,17 @@ + + '{field} is either not an image, or it is not wide or tall enough.', + 'is_image_ratio' => + '{field} is either not an image or not of the right ratio.', + 'is_json' => '{field} contains invalid JSON.', +]; diff --git a/modules/Admin/Language/ro/VideoClip.php b/modules/Admin/Language/ro/VideoClip.php new file mode 100644 index 00000000..638de697 --- /dev/null +++ b/modules/Admin/Language/ro/VideoClip.php @@ -0,0 +1,72 @@ + [ + 'title' => 'Video clips', + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Clip is waiting to be processed.', + 'pending' => 'pending', + 'pending_hint' => 'Clip will be generated shortly.', + 'running' => 'running', + 'running_hint' => 'Clip is being generated.', + 'failed' => 'failed', + 'failed_hint' => 'Clip could not be generated: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Clip was generated successfully!', + ], + 'clip' => 'Clip', + 'duration' => 'Job duration', + ], + 'title' => 'Video clip: {videoClipLabel}', + 'download_clip' => 'Download clip', + 'create' => 'New video clip', + 'go_to_page' => 'Go to clip page', + 'retry' => 'Retry clip generation', + 'delete' => 'Delete clip', + 'logs' => 'Job logs', + 'messages' => [ + 'alreadyExistingError' => 'The video clip you are trying to create already exists!', + 'addToQueueSuccess' => 'Video clip has been added to queue, awaiting to be created!', + 'deleteSuccess' => 'Video clip has been successfully removed!', + ], + 'format' => [ + 'landscape' => 'Landscape', + 'portrait' => 'Portrait', + 'squared' => 'Squared', + ], + 'form' => [ + 'title' => 'New video clip', + 'params_section_title' => 'Video clip parameters', + 'clip_title' => 'Clip title', + 'format' => [ + 'label' => 'Choose a format', + 'landscape_hint' => 'With a 16:9 ratio, landscape videos are great for PeerTube, Youtube and Vimeo.', + 'portrait_hint' => 'With a 9:16 ratio, portrait videos are great for TikTok, Youtube shorts and Instagram stories.', + 'squared_hint' => 'With a 1:1 ratio, squared videos are great for Mastodon, Facebook, Twitter and LinkedIn.', + ], + 'theme' => 'Select a theme', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'trim_start' => 'Trim start', + 'trim_end' => 'Trim end', + 'submit' => 'Create video clip', + ], + 'requirements' => [ + 'title' => 'Missing requirements', + 'missing' => 'You have missing requirements. Make sure to add all the required items to be allowed creating a video for this episode!', + 'ffmpeg' => 'FFmpeg', + 'gd' => 'Graphics Draw (GD)', + 'freetype' => 'Freetype library for GD', + 'transcript' => 'Transcript file (.srt)', + ], +]; diff --git a/modules/Admin/Language/ru/AboutCastopod.php b/modules/Admin/Language/ru/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/ru/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/ru/Breadcrumb.php b/modules/Admin/Language/ru/Breadcrumb.php index 33432cfd..635a3806 100644 --- a/modules/Admin/Language/ru/Breadcrumb.php +++ b/modules/Admin/Language/ru/Breadcrumb.php @@ -19,6 +19,7 @@ return [ 'pages' => 'страниц', 'settings' => 'настройки', 'theme' => 'тема', + 'about' => 'about', 'add' => 'добавить', 'new' => 'новая', 'edit' => 'изменить', @@ -28,15 +29,19 @@ return [ 'publish-date-edit' => 'edit publication date', 'unpublish' => 'снять с публикации', 'delete' => 'удалить', + 'remove' => 'remove', 'fediverse' => 'Федивёрс', - 'block-lists' => 'список блокируемых', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', 'users' => 'пользователи', 'my-account' => 'мой аккаунт', 'change-password' => 'сменить пароль', - 'import' => 'импорт ленты', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', 'platforms' => 'платформы', 'social' => 'социальные сети', 'funding' => 'финансирование', + 'monetization-other' => 'other monetization', 'analytics' => 'аналитика', 'locations' => 'расположение', 'webpages' => 'веб-страницы', diff --git a/modules/Admin/Language/ru/Charts.php b/modules/Admin/Language/ru/Charts.php index 4b33530e..6ede2510 100644 --- a/modules/Admin/Language/ru/Charts.php +++ b/modules/Admin/Language/ru/Charts.php @@ -37,4 +37,5 @@ return [ 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', 'total_storage_by_month' => 'Monthly storage (in MB)', 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', ]; diff --git a/modules/Admin/Language/ru/Common.php b/modules/Admin/Language/ru/Common.php index 596c8bcd..74addcf2 100644 --- a/modules/Admin/Language/ru/Common.php +++ b/modules/Admin/Language/ru/Common.php @@ -40,6 +40,7 @@ return [ ], 'upload_file' => 'Upload a file', 'remote_url' => 'Remote URL', + 'save' => 'Save', ], 'play_episode_button' => [ 'play' => 'Play', diff --git a/modules/Admin/Language/ru/Contributor.php b/modules/Admin/Language/ru/Contributor.php deleted file mode 100644 index d0f3b93d..00000000 --- a/modules/Admin/Language/ru/Contributor.php +++ /dev/null @@ -1,41 +0,0 @@ - 'Podcast contributors', - 'view' => "{username}'s contribution to {podcastTitle}", - 'add' => 'Add contributor', - 'add_contributor' => 'Add a contributor for {0}', - 'edit_role' => 'Update role for {0}', - 'edit' => 'Edit', - 'remove' => 'Remove', - 'list' => [ - 'username' => 'Username', - 'role' => 'Role', - ], - 'form' => [ - 'user' => 'User', - 'user_placeholder' => 'Select a user…', - 'role' => 'Role', - 'role_placeholder' => 'Select its role…', - 'submit_add' => 'Add contributor', - 'submit_edit' => 'Update role', - ], - 'roles' => [ - 'podcast_admin' => 'Podcast admin', - ], - 'messages' => [ - 'removeOwnerError' => "You can't remove the podcast owner!", - 'removeSuccess' => - 'You have successfully removed {username} from {podcastTitle}', - 'alreadyAddedError' => - "The contributor you're trying to add has already been added!", - ], -]; diff --git a/modules/Admin/Language/ru/Episode.php b/modules/Admin/Language/ru/Episode.php index 91313a7c..4fa846e3 100644 --- a/modules/Admin/Language/ru/Episode.php +++ b/modules/Admin/Language/ru/Episode.php @@ -22,6 +22,7 @@ return [ 'all_podcast_episodes' => 'All podcast episodes', 'back_to_podcast' => 'Go back to podcast', 'edit' => 'Edit', + 'preview' => 'Preview', 'publish' => 'Publish', 'publish_edit' => 'Edit publication', 'publish_date_edit' => 'Edit publication date', @@ -55,6 +56,7 @@ return [ }', 'episode' => 'Episode', 'visibility' => 'Visibility', + 'downloads' => 'Downloads', 'comments' => 'Comments', 'actions' => 'Actions', ], @@ -85,7 +87,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ @@ -137,9 +139,9 @@ return [ 'location_name' => 'Location name or address', 'location_name_hint' => 'This can be a real or fictional location', 'transcript' => 'Transcript (subtitles / closed captions)', - 'transcript_hint' => 'Only .srt are allowed.', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', 'transcript_download' => 'Download transcript', - 'transcript_file' => 'Transcript file (.srt)', + 'transcript_file' => 'Transcript file (.srt or .vtt)', 'transcript_remote_url' => 'Remote url for transcript', 'transcript_file_delete' => 'Delete transcript file', 'chapters' => 'Chapters', @@ -210,4 +212,14 @@ return [ 'light' => 'Light', 'light-transparent' => 'Light transparent', ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], ]; diff --git a/modules/Admin/Language/ru/Navigation.php b/modules/Admin/Language/ru/Navigation.php index 68d4609d..f3ffb129 100644 --- a/modules/Admin/Language/ru/Navigation.php +++ b/modules/Admin/Language/ru/Navigation.php @@ -12,12 +12,14 @@ return [ 'toggle_sidebar' => 'Toggle sidebar', 'go_to_website' => 'Go to website', 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', 'dashboard' => 'Dashboard', 'admin' => 'Home', 'podcasts' => 'Podcasts', 'podcast-list' => 'All podcasts', 'podcast-create' => 'New podcast', - 'podcast-import' => 'Import a podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', 'persons' => 'Persons', 'person-list' => 'All persons', 'person-create' => 'New person', @@ -33,6 +35,7 @@ return [ 'settings' => 'Settings', 'settings-general' => 'General', 'settings-theme' => 'Theme', + 'admin-about' => 'About', 'account' => [ 'my-account' => 'My account', 'change-password' => 'Change password', diff --git a/modules/Admin/Language/ru/Platforms.php b/modules/Admin/Language/ru/Platforms.php index ab17d599..e161181c 100644 --- a/modules/Admin/Language/ru/Platforms.php +++ b/modules/Admin/Language/ru/Platforms.php @@ -9,9 +9,22 @@ declare(strict_types=1); */ return [ - 'title' => 'Platforms', + 'title' => [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', 'visible' => 'Display in podcast homepage?', 'on_embed' => 'Display on embeddable player?', 'remove' => 'Remove {platformName}', diff --git a/modules/Admin/Language/ru/Podcast.php b/modules/Admin/Language/ru/Podcast.php index 426b763b..ff0daebc 100644 --- a/modules/Admin/Language/ru/Podcast.php +++ b/modules/Admin/Language/ru/Podcast.php @@ -13,6 +13,7 @@ return [ 'no_podcast' => 'No podcast found!', 'create' => 'Create podcast', 'import' => 'Import podcast', + 'all_imports' => 'Podcast imports', 'new_episode' => 'New Episode', 'view' => 'View podcast', 'edit' => 'Edit podcast', @@ -21,6 +22,7 @@ return [ 'delete' => 'Delete podcast', 'see_episodes' => 'See episodes', 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', 'go_to_page' => 'Go to page', 'latest_episodes' => 'Latest episodes', 'see_all_episodes' => 'See all episodes', @@ -48,7 +50,6 @@ return [ other {# episodes were} } added to the podcast!', 'podcastFeedUpToDate' => 'Podcast is already up to date.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', 'publishError' => 'This podcast is either already published or scheduled for publication.', 'publishEditError' => 'This podcast is not scheduled for publication.', 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', @@ -57,6 +58,8 @@ return [ 'form' => [ 'identity_section_title' => 'Podcast identity', 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + 'cover' => 'Podcast cover', 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', 'banner' => 'Podcast banner', @@ -71,7 +74,17 @@ return [ 'episodic' => 'Episodic', 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', 'serial' => 'Serial', - 'serial_hint' => 'If episodes are intended to be consumed in sequential order. The oldest episodes will be presented first.', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', ], 'description' => 'Description', 'classification_section_title' => 'Classification', @@ -96,6 +109,8 @@ return [ 'owner_email' => 'Owner email', 'owner_email_hint' => 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', 'publisher' => 'Publisher', 'publisher_hint' => 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', @@ -110,6 +125,11 @@ return [ 'premium' => 'Premium', 'premium_by_default' => 'Episodes must be set as premium by default', 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', 'payment_pointer' => 'Payment Pointer for Web Monetization', 'payment_pointer_hint' => 'This is your where you will receive money thanks to Web Monetization', @@ -118,11 +138,12 @@ return [ 'If you need RSS tags that Castopod does not handle, set them here.', 'custom_rss' => 'Custom RSS tags for the podcast', 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', 'new_feed_url' => 'New feed URL', 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', 'partnership' => 'Partnership', 'partner_id' => 'ID', 'partner_link_url' => 'Link URL', @@ -130,7 +151,6 @@ return [ 'partner_id_hint' => 'Your own partner ID', 'partner_link_url_hint' => 'The generic partner link address', 'partner_image_url_hint' => 'The generic partner image address', - 'status_section_title' => 'Status', 'block' => 'Podcast should be hidden from public catalogues', 'block_hint' => 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', diff --git a/modules/Admin/Language/ru/PodcastImport.php b/modules/Admin/Language/ru/PodcastImport.php deleted file mode 100644 index 7c3ef67d..00000000 --- a/modules/Admin/Language/ru/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'The podcast to import', - 'old_podcast_section_subtitle' => - 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', - 'imported_feed_url' => 'Feed URL', - 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', - 'new_podcast_section_title' => 'The new podcast', - 'advanced_params_section_title' => 'Advanced parameters', - 'advanced_params_section_subtitle' => - 'Keep the default values if you have no idea of what the fields are for.', - 'slug_field' => 'Field to be used to calculate episode slug', - 'description_field' => - 'Source field used for episode description / show notes', - 'force_renumber' => 'Force episodes renumbering', - 'force_renumber_hint' => - 'Use this if your podcast does not have episode numbers but wish to set them during import.', - 'season_number' => 'Season number', - 'season_number_hint' => - 'Use this if your podcast does not have a season number but wish to set one during import. Leave blank otherwise.', - 'max_episodes' => 'Maximum number of episodes to import', - 'max_episodes_hint' => 'Leave blank to import all episodes', - 'lock_import' => - 'This feed is protected. You cannot import it. If you are the owner, unprotect it on the origin platform.', - 'submit' => 'Import podcast', -]; diff --git a/modules/Admin/Language/ru/PodcastNavigation.php b/modules/Admin/Language/ru/PodcastNavigation.php index b4d7ddc0..bb777707 100644 --- a/modules/Admin/Language/ru/PodcastNavigation.php +++ b/modules/Admin/Language/ru/PodcastNavigation.php @@ -10,10 +10,13 @@ declare(strict_types=1); return [ 'go_to_page' => 'Go to podcast page', + 'rss_feed' => 'RSS feed', 'dashboard' => 'Podcast dashboard', 'podcast-view' => 'Home', 'podcast-edit' => 'Edit podcast', 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', 'episodes' => 'Episodes', 'episode-list' => 'All episodes', 'episode-create' => 'New episode', @@ -25,14 +28,15 @@ return [ 'podcast-analytics-players' => 'Players', 'podcast-analytics-listening-time' => 'Listening time', 'podcast-analytics-time-periods' => 'Time periods', - 'premium' => 'Premium', + 'monetization' => 'Monetization', 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', + 'subscription-create' => 'Add subscription', 'contributors' => 'Contributors', 'contributor-list' => 'All contributors', 'contributor-add' => 'Add contributor', - 'platforms' => 'External platforms', - 'platforms-podcasting' => 'Podcasting', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', 'platforms-social' => 'Social networks', - 'platforms-funding' => 'Funding', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', ]; diff --git a/modules/Admin/Language/ru/User.php b/modules/Admin/Language/ru/User.php deleted file mode 100644 index 585d6799..00000000 --- a/modules/Admin/Language/ru/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', - 'ban' => 'Ban', - 'unban' => 'Unban', - 'delete' => 'Delete', - 'create' => 'New user', - 'view' => "{username}'s info", - 'all_users' => 'All users', - 'list' => [ - 'user' => 'User', - 'roles' => 'Roles', - 'banned' => 'Banned?', - ], - 'form' => [ - 'email' => 'Email', - 'username' => 'Username', - 'password' => 'Password', - 'new_password' => 'New Password', - 'roles' => 'Roles', - 'permissions' => 'Permissions', - 'submit_create' => 'Create user', - 'submit_edit' => 'Save', - 'submit_password_change' => 'Change!', - ], - 'roles' => [ - 'superadmin' => 'Super admin', - ], - 'messages' => [ - 'createSuccess' => - 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => - "{username}'s roles have been successfully updated.", - 'forcePassResetSuccess' => - '{username} will be prompted with a password reset upon next visit.', - 'banSuccess' => '{username} has been banned.', - 'unbanSuccess' => '{username} has been unbanned.', - 'editOwnerError' => - '{username} is the instance owner, you cannot edit its roles.', - 'banSuperAdminError' => - '{username} is a superadmin, one does not simply ban a superadmin…', - 'deleteSuperAdminError' => - '{username} is a superadmin, one does not simply delete a superadmin…', - 'deleteSuccess' => '{username} has been deleted.', - ], -]; diff --git a/modules/Admin/Language/ru/Validation.php b/modules/Admin/Language/ru/Validation.php index 750b1968..f76c3163 100644 --- a/modules/Admin/Language/ru/Validation.php +++ b/modules/Admin/Language/ru/Validation.php @@ -13,6 +13,5 @@ return [ '{field} is either not an image, or it is not wide or tall enough.', 'is_image_ratio' => '{field} is either not an image or not of the right ratio.', - 'validate_url' => - 'The {field} field must be a valid URL (eg. https://example.com/).', + 'is_json' => '{field} contains invalid JSON.', ]; diff --git a/modules/Admin/Language/sk/AboutCastopod.php b/modules/Admin/Language/sk/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/sk/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/sk/Breadcrumb.php b/modules/Admin/Language/sk/Breadcrumb.php index 3c742281..26cf0b17 100644 --- a/modules/Admin/Language/sk/Breadcrumb.php +++ b/modules/Admin/Language/sk/Breadcrumb.php @@ -14,29 +14,34 @@ return [ ->gateway => 'Úvod', 'podcasts' => 'podcasty', 'episodes' => 'časti', - 'subscriptions' => 'subscriptions', + 'subscriptions' => 'odbery', 'contributors' => 'prispievatelia', 'pages' => 'stránky', 'settings' => 'nastavenia', 'theme' => 'vzhľad', + 'about' => 'informácie', 'add' => 'pridať', 'new' => 'pridať', 'edit' => 'upraviť', 'persons' => 'osobnosti', 'publish' => 'zverejniť', 'publish-edit' => 'upraviť zverejnené', - 'publish-date-edit' => 'edit publication date', + 'publish-date-edit' => 'upraviť dátum publikovania', 'unpublish' => 'zrušiť zverejnenie', 'delete' => 'vymazať', + 'remove' => 'odstrániť', 'fediverse' => 'fediverse', - 'block-lists' => 'zoznamy blokovaných', + 'blocked-actors' => 'zablokovaní aktéri', + 'blocked-domains' => 'zablokované domény', 'users' => 'používatelia', 'my-account' => 'môj účet', 'change-password' => 'zmeniť heslo', - 'import' => 'import kanála', + 'imports' => 'nahratia', + 'sync-feeds' => 'synchronize feeds', 'platforms' => 'platformy', 'social' => 'sociálne siete', 'funding' => 'financovanie', + 'monetization-other' => 'other monetization', 'analytics' => 'analytika', 'locations' => 'miesta', 'webpages' => 'web stránky', @@ -48,5 +53,5 @@ return [ 'video-clips' => 'video klipy', 'embed' => 'vnorený', 'notifications' => 'oboznámenia', - 'suspend' => 'suspend', + 'suspend' => 'pozastaviť', ]; diff --git a/modules/Admin/Language/sk/Charts.php b/modules/Admin/Language/sk/Charts.php index 8c29dff3..17c1187e 100644 --- a/modules/Admin/Language/sk/Charts.php +++ b/modules/Admin/Language/sk/Charts.php @@ -37,4 +37,5 @@ return [ 'podcast_by_bandwidth' => 'Denný prenos údajov (v MB)', 'total_storage_by_month' => 'Mesačné úložisko (v MB)', 'total_bandwidth_by_month' => 'Mesačný prenos údajov (v MB)', + 'total_bandwidth_by_month_limit' => 'Obmedzený na {totalBandwidth} za mesiac', ]; diff --git a/modules/Admin/Language/sk/Common.php b/modules/Admin/Language/sk/Common.php index 9572c953..584c05bf 100644 --- a/modules/Admin/Language/sk/Common.php +++ b/modules/Admin/Language/sk/Common.php @@ -40,6 +40,7 @@ return [ ], 'upload_file' => 'Nahrať súbor', 'remote_url' => 'Vzdialená adresa URL', + 'save' => 'Uložiť', ], 'play_episode_button' => [ 'play' => 'Prehrať', diff --git a/modules/Admin/Language/sk/Countries.php b/modules/Admin/Language/sk/Countries.php index 1077917e..8d5fa7cd 100644 --- a/modules/Admin/Language/sk/Countries.php +++ b/modules/Admin/Language/sk/Countries.php @@ -13,7 +13,7 @@ declare(strict_types=1); return [ 'AD' => 'Andorra', 'AE' => 'Spojené Arabské Emiráty', - 'AF' => 'Afghanistan', + 'AF' => 'Afganistan', 'AG' => 'Antigua a Barbuda', 'AI' => 'Anguilla', 'AL' => 'Albánsko', @@ -25,7 +25,7 @@ return [ 'AT' => 'Rakúsko', 'AU' => 'Austrália', 'AW' => 'Aruba', - 'AX' => 'Åland Islands', + 'AX' => 'Ålandy', 'AZ' => 'Azerbajdžan', 'BA' => 'Bosna a Hercegovina', 'BB' => 'Barbados', @@ -33,36 +33,41 @@ return [ 'BE' => 'Belgicko', 'BF' => 'Burkina Faso', 'BG' => 'Bulharsko', - 'BH' => 'Bahrain', + 'BH' => 'Bahrajn', 'BI' => 'Burundi', 'BJ' => 'Benin', - 'BL' => 'Saint Barthélemy', - 'BM' => 'Bermuda', - 'BN' => 'Brunei Darussalam', - 'BO' => 'Bolivia, Plurinational State of', - 'BQ' => 'Bonaire, Sint Eustatius and Saba', - 'BR' => 'Brazil', - 'BS' => 'Bahamas', - 'BT' => 'Bhutan', - 'BV' => 'Bouvet Island', + 'BL' => 'Svätý Bartolomej', + 'BM' => 'Bermudy', + 'BN' => 'Brunejsko-Darussalamsky Štát', + 'BO' => 'Bolívia, plurinský štát', + 'BQ' => 'Bonaire, Sint Eustatius a Sabaj', + 'BR' => 'Brazília', + 'BS' => 'Bahamy', + 'BT' => 'Bhután', + 'BV' => 'Bouvetov ostrov', 'BW' => 'Botswana', - 'BY' => 'Belarus', + 'BY' => 'Bielorusko', 'BZ' => 'Belize', - 'CA' => 'Canada', - 'CC' => 'Cocos (Keeling) Islands', - 'CD' => 'Congo, the Democratic Republic of the', - 'CF' => 'Central African Republic', - 'CG' => 'Congo', - 'CH' => 'Switzerland', - 'CI' => "Côte d'Ivoire", - 'CK' => 'Cook Islands', - 'CL' => 'Chile', - 'CM' => 'Cameroon', + 'CA' => 'Kanada', + 'CC' => 'Kokosové ostrovy', + 'CD' => ' +Bhután +Bouvetov ostrov +Bielorusko +Kokosové ostrovy +Konžská demokratická republika', + 'CF' => 'Stredoafrická republika', + 'CG' => 'Kongo', + 'CH' => 'Švajčiarsko', + 'CI' => "Pobrežie slonoviny", + 'CK' => 'Cookove ostrovy', + 'CL' => 'Čile', + 'CM' => 'Kamerun', 'CN' => 'Čína', 'CO' => 'Kolumbia', 'CR' => 'Kostarika', 'CU' => 'Kuba', - 'CV' => 'Cape Verde', + 'CV' => 'Kapverdy', 'CW' => 'Curaçao', 'CX' => 'Vianočný Ostrov', 'CY' => 'Cyprus', @@ -70,7 +75,7 @@ return [ 'DE' => 'Nemecko', 'DJ' => 'Džibutsko', 'DK' => 'Dánsko', - 'DM' => 'Dominica', + 'DM' => 'Dominika', 'DO' => 'Dominikánska republika', 'DZ' => 'Alžírsko', 'EC' => 'Ekvádor', @@ -83,22 +88,22 @@ return [ 'FI' => 'Fínsko', 'FJ' => 'Fidži', 'FK' => 'Falklandské ostrovy (Malvíny)', - 'FM' => 'Micronesia, Federated States of', + 'FM' => 'Mikronézia, federatívne štáty', 'FO' => 'Faerské Ostrovy', 'FR' => 'Francúzsko', 'GA' => 'Gabon', 'GB' => 'Spojené Kráľovstvo', 'GD' => 'Grenada', 'GE' => 'Gruzínsko', - 'GF' => 'French Guiana', + 'GF' => 'Francúzska Guayana', 'GG' => 'Guernsey', 'GH' => 'Ghana', - 'GI' => 'Gibraltar', + 'GI' => 'Gibraltár', 'GL' => 'Grónsko', 'GM' => 'Gambia', 'GN' => 'Guinea', 'GP' => 'Guadeloupe', - 'GQ' => 'Equatorial Guinea', + 'GQ' => 'Rovníková Guinea', 'GR' => 'Grécko', 'GS' => 'Južná Georgia a Južné Sandwichove ostrovy', 'GT' => 'Guatemala', @@ -113,7 +118,7 @@ return [ 'HU' => 'Maďarsko', 'ID' => 'Indonézia', 'IE' => 'Írsko', - 'IL' => 'Israel', + 'IL' => 'Izrael', 'IM' => 'Ostrov Man', 'IN' => 'India', 'IO' => 'Britské indickooceánske územie', @@ -129,18 +134,31 @@ return [ 'KG' => 'Kirgizsko', 'KH' => 'Kambodža', 'KI' => 'Kiribati', - 'KM' => 'Comoros', - 'KN' => 'Saint Kitts and Nevis', - 'KP' => "Korea, Democratic People's Republic of", - 'KR' => 'Korea, Republic of', - 'KW' => 'Kuwait', - 'KY' => 'Cayman Islands', - 'KZ' => 'Kazakhstan', - 'LA' => "Lao People's Democratic Republic", + 'KM' => 'Komory', + 'KN' => 'Svätý Krištof a Nevis', + 'KP' => "Kórea, Demokratická ľudová republika", + 'KR' => 'Kórejská Republika', + 'KW' => 'Kuvajt', + 'KY' => 'Kajmanské ostrovy', + 'KZ' => 'Kazachstan', + 'LA' => "_03 slovutny poprad. mp3 +Kapverdy +Dominika +Mikronézia, federatívne štáty +Francúzska Guayana +Rovníková Guinea +Komory +Svätý Krištof a Nevis +Kórea, Demokratická ľudová republika +Kórejská Republika +Kuvajt +Kajmanské ostrovy +Kazachstan +Laoská ľudovodemokratická Republika", 'LB' => 'Libanon', 'LC' => 'Svätá Lucia', 'LI' => 'Lichtenštajnsko', - 'LK' => 'Sri Lanka', + 'LK' => 'Srí Lanka', 'LR' => 'Libéria', 'LS' => 'Lesotho', 'LT' => 'Litva', @@ -151,30 +169,30 @@ return [ 'MC' => 'Monako', 'MD' => 'Moldavská Republika', 'ME' => 'Čierna Hora', - 'MF' => 'Saint Martin (French part)', + 'MF' => 'Svätý Martin (Francúzska časť)', 'MG' => 'Madagaskar', - 'MH' => 'Marshall Islands', + 'MH' => 'Marshallove ostrovy', 'MK' => 'Macedónsko-Bývalá Juhoslovanská Republika', 'ML' => 'Mali', 'MM' => 'Mjanmarsko', 'MN' => 'Mongolsko', 'MO' => 'Macao', - 'MP' => 'Northern Mariana Islands', - 'MQ' => 'Martinique', - 'MR' => 'Mauritania', + 'MP' => 'Severné Mariány', + 'MQ' => 'Martinik', + 'MR' => 'Mauritánia', 'MS' => 'Montserrat', 'MT' => 'Malta', - 'MU' => 'Mauritius', - 'MV' => 'Maldives', + 'MU' => 'Maurícius', + 'MV' => 'Maledivy', 'MW' => 'Malawi', 'MX' => 'Mexiko', 'MY' => 'Malajzia', - 'MZ' => 'Mozambique', + 'MZ' => 'Mozambik', 'N/A' => 'Not Applicable (local IP…)', - 'NA' => 'Namibia', + 'NA' => 'Namíbia', 'NC' => 'Nová Kaledónia', 'NE' => 'Niger', - 'NF' => 'Norfolk Island', + 'NF' => 'Ostrov Norfolk', 'NG' => 'Nigéria', 'NI' => 'Nikaragua', 'NL' => 'Holandsko', @@ -186,64 +204,64 @@ return [ 'OM' => 'Omán', 'PA' => 'Panama', 'PE' => 'Peru', - 'PF' => 'French Polynesia', - 'PG' => 'Papua New Guinea', - 'PH' => 'Philippines', + 'PF' => 'Francúzska Polynézia', + 'PG' => 'Papua-Nová Guinea', + 'PH' => 'Filipíny', 'PK' => 'Pakistan', - 'PL' => 'Poland', - 'PM' => 'Saint Pierre and Miquelon', - 'PN' => 'Pitcairn', - 'PR' => 'Puerto Rico', + 'PL' => 'Poľsko', + 'PM' => 'Svätý Peter a Michal', + 'PN' => 'Pitcairnove ostrovy', + 'PR' => 'Portoriko', 'PS' => 'Palestínske okupované územia', 'PT' => 'Portugalsko', 'PW' => 'Palau', - 'PY' => 'Paraguay', - 'QA' => 'Qatar', + 'PY' => 'Paraguaj', + 'QA' => 'Katar', 'RE' => 'Réunion', - 'RO' => 'Romania', - 'RS' => 'Serbia', + 'RO' => 'Rumunsko', + 'RS' => 'Srbsko', 'RU' => 'Ruská Federácia', 'RW' => 'Rwanda', - 'SA' => 'Saudi Arabia', - 'SB' => 'Solomon Islands', - 'SC' => 'Seychelles', - 'SD' => 'Sudan', - 'SE' => 'Sweden', - 'SG' => 'Singapore', - 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', + 'SA' => 'Saudská Arábia', + 'SB' => 'Šalamúnove ostrovy', + 'SC' => 'Seychely', + 'SD' => 'Sudán', + 'SE' => 'Švédsko', + 'SG' => 'Singapur', + 'SH' => 'Svätá Helena, Ascension a Tristan da Cunha', 'SI' => 'Slovinsko', - 'SJ' => 'Svalbard and Jan Mayen', + 'SJ' => 'Špicbergy a Jan Mayen', 'SK' => 'Slovensko', 'SL' => 'Sierra Leone', - 'SM' => 'San Marino', + 'SM' => 'San Maríno', 'SN' => 'Senegal', 'SO' => 'Somálsko', - 'SR' => 'Suriname', + 'SR' => 'Surinam', 'SS' => 'Južný Sudán', - 'ST' => 'Sao Tome and Principe', + 'ST' => 'Svätý Tomáš a Principov ostrov', 'SV' => 'Salvádor', 'SX' => 'Sint Maarten (Dutch part)', - 'SY' => 'Syrian Arab Republic', + 'SY' => 'Sýrska Arabská Republika', 'SZ' => 'Svazijsko', 'TC' => 'Turks and Caicos Islands', - 'TD' => 'Chad', + 'TD' => 'Čad', 'TF' => 'Francúzske južné územia', 'TG' => 'Togo', 'TH' => 'Thajsko', 'TJ' => 'Tadžikistan', 'TK' => 'Tokelau', - 'TL' => 'Timor-Leste', + 'TL' => 'Východný Timor', 'TM' => 'Turkménsko', 'TN' => 'Tunisko', 'TO' => 'Tonga', 'TR' => 'Turecko', - 'TT' => 'Trinidad and Tobago', + 'TT' => 'Trinidad a Tobago', 'TV' => 'Tuvalu', 'TW' => 'Taiwan, provincia Číny', 'TZ' => 'Tanzánia', 'UA' => 'Ukraina', 'UG' => 'Uganda', - 'UM' => 'United States Minor Outlying Islands', + 'UM' => 'Menšie Odľahlé Ostrovy Spojených Štátov', 'US' => 'Spojené Štáty', 'UY' => 'Uruguaj', 'UZ' => 'Uzbekistán', @@ -254,7 +272,7 @@ return [ 'VI' => 'Americké Panenské ostrovy', 'VN' => 'Vietnam', 'VU' => 'Vanuatu', - 'WF' => 'Wallis and Futuna', + 'WF' => 'Wallis a Futuna', 'WS' => 'Samoa', 'YE' => 'Jemen', 'YT' => 'Mayotte', diff --git a/modules/Admin/Language/sk/Episode.php b/modules/Admin/Language/sk/Episode.php index a731c0e2..ac561290 100644 --- a/modules/Admin/Language/sk/Episode.php +++ b/modules/Admin/Language/sk/Episode.php @@ -24,16 +24,17 @@ return [ 'all_podcast_episodes' => 'Všetky epizódy podcastu', 'back_to_podcast' => 'Späť na podcast', 'edit' => 'Upraviť', + 'preview' => 'Náhľad', 'publish' => 'Zverejniť', 'publish_edit' => 'Upraviť zverejnenie', - 'publish_date_edit' => 'Edit publication date', + 'publish_date_edit' => 'Upraviť dátum zverejnenia', 'unpublish' => 'Zrušiť zverejnenie', 'publish_error' => 'Epizóda je už zverejnená.', 'publish_edit_error' => 'Epizóda je už zverejnená.', 'publish_cancel_error' => 'Epizóda je už zverejnená.', - 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', - 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', - 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'publish_date_edit_error' => 'Epizóda zatiaľ nie je zverejnená, nie je možné upraviť dátum zverejnenia.', + 'publish_date_edit_future_error' => 'Dátum zverejnenia musí byť v minulosti! Ak si zverejnenie želáte naplánovať v budúcnosti, musíte ho najskôr zrušiť.', + 'publish_date_edit_success' => 'Dátum zverejnenia epizódy bol úspešne aktualizovaný!', 'unpublish_error' => 'Epizóda nie je zverejnená.', 'delete' => 'Vymazať', 'go_to_page' => 'Prejsť na stránku', @@ -59,6 +60,7 @@ return [ }', 'episode' => 'Epizóda', 'visibility' => 'Viditeľnosť', + 'downloads' => 'Stiahnutia', 'comments' => 'Komentáre', 'actions' => 'Úkony', ], @@ -83,56 +85,56 @@ return [ audio {zvuk} other {médiá} }.', - 'deleteFileError' => 'Nepodarilo sa vymazať {type, select, - transcript {prepis} - chapters {kapitoly} - image {obrázok} - audio {zvuk} - other {médiá} - } súbor {file_path}. Môžete ho z disku odstrániť ručne.', + 'deleteFileError' => 'Failed to delete {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'Epizóda s takýmto trvalým odkazom už existuje.', ], 'form' => [ 'file_size_error' => - 'Your file size is too big! Max size is {0}. Increase the `memory_limit`, `upload_max_filesize` and `post_max_size` values in your php configuration file then restart your web server to upload your file.', + 'Súbor je príliš veľký! Maximálna povolená veľkosť je {0}. V konfigurácii Php zvýšte hodnoty nastavení `memory_limit`, `upload_max_filesize` a `post_max_size` a následne reštartujte web server, aby ste súbor mohli nahrať znovu.', 'audio_file' => 'Zvukový súbor', 'audio_file_hint' => 'Vyberte zvukový súbor .mp3, alebo .m4a.', 'info_section_title' => 'Informácie o časti', 'cover' => 'Obal k časti', 'cover_hint' => - 'If you do not set a cover, the podcast cover will be used instead.', - 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'Ak obrázok nepridáte, použije sa obrázok podcastu.', + 'cover_size_hint' => 'Obrázok musí byť štvorcový minimálny rozmer 1400px.', 'title' => 'Názov', 'title_hint' => - 'Should contain a clear and concise episode name. Do not specify the episode or season numbers here.', + 'Má obsahovať jasný a výstižný názov. Nepridávajte čísla sérií a epizód.', 'permalink' => 'Trvalý odkaz', - 'season_number' => 'Season', + 'season_number' => 'Séria', 'episode_number' => 'Epizóda', 'type' => [ - 'label' => 'Type', - 'full' => 'Full', - 'full_hint' => 'Complete content (the episode)', - 'trailer' => 'Trailer', - 'trailer_hint' => 'Short, promotional piece of content that represents a preview of the current show', + 'label' => 'Typ', + 'full' => 'Celá epizóda', + 'full_hint' => '´Uplný obsah', + 'trailer' => 'Upútavka', + 'trailer_hint' => 'Krátky promočný úryvok obsahu, ktorý slúži ako ukážka podcastu', 'bonus' => 'Bonus', - 'bonus_hint' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show', + 'bonus_hint' => 'Doplnkový obsah podcastu (Informácie zo zákulisia alebo rozhovor s účinkujúcimi) alebo upútavka iného podcastu', ], - 'premium_title' => 'Premium', - 'premium' => 'Episode must be accessible to premium subscribers only', + 'premium_title' => 'Prémiový obsah', + 'premium' => 'Epizóda je prístupná len pre predplatiteľov prémiového obsahu', 'parental_advisory' => [ - 'label' => 'Parental advisory', - 'hint' => 'Does the episode contain explicit content?', - 'undefined' => 'undefined', - 'clean' => 'Clean', + 'label' => 'Rodičovská kontrola', + 'hint' => 'Obsahuje epizóda explicitný obsah?', + 'undefined' => 'neuvedené', + 'clean' => 'Čisté', 'explicit' => 'Chúlostivé', ], - 'show_notes_section_title' => 'Show notes', + 'show_notes_section_title' => 'Poznámky epizódy', 'show_notes_section_subtitle' => - 'Up to 4000 characters, be clear and concise. Show notes help potential listeners in finding the episode.', + 'Maximálne 4000 znakov, buďte jasní a výstižní.', 'description' => 'Popis', - 'description_footer' => 'Description footer', + 'description_footer' => 'Päta popisu', 'description_footer_hint' => - 'This text is added at the end of each episode description, it is a good place to input your social links for example.', + 'Tento text je pridaný na koniec popisu každej epizódy, je vhodný napríklad na zverejnenie odkazov na sociálne siete.', 'additional_files_section_title' => 'Dodatočné súbory', 'additional_files_section_subtitle' => 'Tieto súbory sú určené na použitie s inými platformami s cieľom poslucháčom poskytovať bohačšiu skúsenosť. Pre viac informácií si pozrite {podcastNamespaceLink}.', @@ -141,9 +143,9 @@ return [ 'location_name' => 'Názov oblasti alebo adresa', 'location_name_hint' => 'Môže to byť skutočné alebo vymyslené miesto', 'transcript' => 'Prepis (titulky / skryté titulky)', - 'transcript_hint' => 'Povolené sú len súbory .srt.', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', 'transcript_download' => 'Stiahnuť prepis', - 'transcript_file' => 'Súbor s prepisom (.srt)', + 'transcript_file' => 'Transcript file (.srt or .vtt)', 'transcript_remote_url' => 'Vzdialená adresa Url s prepisom', 'transcript_file_delete' => 'Vymazať súbor s prepisom', 'chapters' => 'Kapitoly', @@ -187,13 +189,13 @@ return [ 'message_warning_submit' => 'Napriek tomu zverejniť', ], 'publish_date_edit_form' => [ - 'new_publication_date' => 'New publication date', - 'new_publication_date_hint' => 'Must be set to a past date.', - 'submit' => 'Edit publication date', + 'new_publication_date' => 'Nový dátum zverejnenia', + 'new_publication_date_hint' => 'Musí byť dátum v minulosti.', + 'submit' => 'Upraviť dátum zverejnenia', ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", + "Zrušenie zverejnenia odstráni všetky pridružené príspevky a komentáre a odstráni epizódu z kanála RSS.", 'understand' => 'Rozumiem, chcem zrušiť zverejnenie epizódy', 'submit' => 'Zrušiť zverejnenie', ], @@ -210,8 +212,18 @@ return [ 'clipboard_iframe' => 'Skopírovať kód prehrávača do schránky', 'clipboard_url' => 'Skopírovať adresu do schránky', 'dark' => 'Tmavý', - 'dark-transparent' => 'Dark transparent', - 'light' => 'Light', - 'light-transparent' => 'Light transparent', + 'dark-transparent' => 'Tmavý priehľadný', + 'light' => 'Svetlý', + 'light-transparent' => 'Svetlý priehľadný', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'konceptový režim', + 'text' => '{publication_status, select, + published {Táto epizóda ešte nieje zverejnená.} + scheduled {Táto epizóda je naplánovaná na zverejnenie {publication_date}.} + with_podcast {Táto epizóda bude zverejnená zarovno s podcastom.} + other {Táto epizóda ešte nieje zverejnená.} + }', + 'preview' => 'Náhľad', ], ]; diff --git a/modules/Admin/Language/sk/EpisodeNavigation.php b/modules/Admin/Language/sk/EpisodeNavigation.php index d60ef396..8414aebe 100644 --- a/modules/Admin/Language/sk/EpisodeNavigation.php +++ b/modules/Admin/Language/sk/EpisodeNavigation.php @@ -10,14 +10,14 @@ declare(strict_types=1); return [ 'go_to_page' => 'Zobraziť stránku časti', - 'dashboard' => 'Episode dashboard', + 'dashboard' => 'Nástenka epizódy', 'episode-view' => 'Domov', 'episode-edit' => 'Upraviť časť', - 'episode-persons-manage' => 'Manage persons', + 'episode-persons-manage' => 'Spravovať osobnosti', 'embed-add' => 'Vnorený prehrávač', 'clips' => 'Klipy', - 'video-clips-list' => 'Video clips', - 'video-clips-create' => 'New video clip', - 'soundbites-list' => 'Soundbites', - 'soundbites-create' => 'New soundbite', + 'video-clips-list' => 'Video klipy', + 'video-clips-create' => 'Nový video klip', + 'soundbites-list' => 'Zvukové ukážky', + 'soundbites-create' => 'Nová zvučka', ]; diff --git a/modules/Admin/Language/sk/Fediverse.php b/modules/Admin/Language/sk/Fediverse.php index 1a5e0a27..69cf2fb5 100644 --- a/modules/Admin/Language/sk/Fediverse.php +++ b/modules/Admin/Language/sk/Fediverse.php @@ -11,22 +11,22 @@ declare(strict_types=1); return [ 'messages' => [ 'actorNotFound' => 'Účet nieje možné nájsť!', - 'blockActorSuccess' => '{actor} has been blocked!', - 'unblockActorSuccess' => 'Actor has been unblocked!', - 'blockDomainSuccess' => '{domain} has been blocked!', - 'unblockDomainSuccess' => '{domain} has been unblocked!', + 'blockActorSuccess' => '{actor} bol/i zablokovaný!', + 'unblockActorSuccess' => 'Aktér bol odblokovaný!', + 'blockDomainSuccess' => '{domain} bola zablokovaná!', + 'unblockDomainSuccess' => '{domain} bola odblokovaná!', ], - 'blocked_actors' => 'Zablokované účty', + 'blocked_actors' => 'Blokované účty', 'blocked_domains' => 'Zablokované domény', 'block_lists_form' => [ 'handle' => 'Account handle', 'handle_hint' => 'Input @username@domain account.', - 'domain' => 'Domain name', - 'submit' => 'Block!', + 'domain' => 'Názov domény', + 'submit' => 'Blokovať!', ], 'list' => [ 'actor' => 'Účet', - 'domain' => 'Domain name', - 'unblock' => 'Unblock', + 'domain' => 'Názov domény', + 'unblock' => 'Odblokovať', ], ]; diff --git a/modules/Admin/Language/sk/Install.php b/modules/Admin/Language/sk/Install.php index 6282e10b..c9669cdf 100644 --- a/modules/Admin/Language/sk/Install.php +++ b/modules/Admin/Language/sk/Install.php @@ -13,8 +13,8 @@ return [ 'manual_config_subtitle' => 'Create a `.env` file with your settings and refresh the page to continue installation.', 'form' => [ - 'instance_config' => 'Instance configuration', - 'hostname' => 'Hostname', + 'instance_config' => 'Nastavenie inštancie', + 'hostname' => 'Názov hostiteľa', 'media_base_url' => 'Media base URL', 'media_base_url_hint' => 'If you use a CDN and/or an external analytics service, you may set them here.', @@ -24,17 +24,17 @@ return [ 'auth_gateway' => 'Auth gateway', 'auth_gateway_hint' => 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', - 'database_config' => 'Database configuration', + 'database_config' => 'Nastavenie databázy', 'database_config_hint' => 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', - 'db_hostname' => 'Database hostname', - 'db_name' => 'Database name', - 'db_username' => 'Database username', - 'db_password' => 'Database password', - 'db_prefix' => 'Database prefix', + 'db_hostname' => 'Server databázy', + 'db_name' => 'Názov databázy', + 'db_username' => 'Prihlasovacie meno do databázy', + 'db_password' => 'Heslo databázy', + 'db_prefix' => 'Prefix databázy', 'db_prefix_hint' => "The prefix of the Castopod table names, leave as is if you don't know what it means.", - 'cache_config' => 'Cache configuration', + 'cache_config' => 'Nastavenie cache', 'cache_config_hint' => 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', 'cache_handler' => 'Obslužný mechanizmus vyrovnávacej pamäte', diff --git a/modules/Admin/Language/sk/Navigation.php b/modules/Admin/Language/sk/Navigation.php index f2c9e53a..2183aec0 100644 --- a/modules/Admin/Language/sk/Navigation.php +++ b/modules/Admin/Language/sk/Navigation.php @@ -12,12 +12,14 @@ return [ 'toggle_sidebar' => 'Prepnúť postranný panel', 'go_to_website' => 'Prejsť na webstránku', 'go_to_admin' => 'Spravovať', + 'not-authorized' => 'Neautorizovaný', 'dashboard' => 'Nástenka', 'admin' => 'Úvod', 'podcasts' => 'Podcasty', 'podcast-list' => 'Všetky podcasty', 'podcast-create' => 'Nový podcast', - 'podcast-import' => 'Importovať podcast', + 'all-podcast-imports' => 'Všetky nahrané podcasty', + 'podcast-imports-add' => 'Importovať podcast', 'persons' => 'Osobnosti', 'person-list' => 'Všetky osobnosti', 'person-create' => 'Nová osobnosť', @@ -33,6 +35,7 @@ return [ 'settings' => 'Nastavenia', 'settings-general' => 'Všeobecné', 'settings-theme' => 'Vzhľad', + 'admin-about' => 'O aplikácii', 'account' => [ 'my-account' => 'Môj účet', 'change-password' => 'Zmeniť heslo', diff --git a/modules/Admin/Language/sk/Page.php b/modules/Admin/Language/sk/Page.php index 72892ebf..9b2fa2e6 100644 --- a/modules/Admin/Language/sk/Page.php +++ b/modules/Admin/Language/sk/Page.php @@ -15,16 +15,16 @@ return [ 'create' => 'Nová stránka', 'go_to_page' => 'Prejsť na stránku', 'edit' => 'Upraviť stránku', - 'delete' => 'Delete page', + 'delete' => 'Vymazať stránku', 'form' => [ - 'title' => 'Title', - 'permalink' => 'Permalink', - 'content' => 'Content', - 'submit_create' => 'Create page', - 'submit_edit' => 'Save', + 'title' => 'Názov', + 'permalink' => 'Trvalý odkaz', + 'content' => 'Obsah', + 'submit_create' => 'Vytvoriť stránku', + 'submit_edit' => 'Uložiť', ], 'messages' => [ - 'createSuccess' => 'The page “{pageTitle}” was created successfully!', - 'editSuccess' => 'The page was successfully updated!', + 'createSuccess' => 'Stránka “{pageTitle}” bola úspešne vytvorená!', + 'editSuccess' => 'Stránka bola úspešne aktualizovaná!', ], ]; diff --git a/modules/Admin/Language/sk/Pager.php b/modules/Admin/Language/sk/Pager.php index e25ee638..43b98de4 100644 --- a/modules/Admin/Language/sk/Pager.php +++ b/modules/Admin/Language/sk/Pager.php @@ -9,13 +9,13 @@ declare(strict_types=1); */ return [ - 'pageNavigation' => 'Page navigation', - 'first' => 'First', - 'previous' => 'Previous', - 'next' => 'Next', - 'last' => 'Last', - 'older' => 'Older', - 'newer' => 'Newer', + 'pageNavigation' => 'Navigácia stránky', + 'first' => 'Prvá', + 'previous' => 'Predošlá', + 'next' => 'Ďalšia', + 'last' => 'Posledná', + 'older' => 'Staršia', + 'newer' => 'Novšia', 'invalidTemplate' => '{0} is not a valid Pager template.', 'invalidPaginationGroup' => '{0} is not a valid Pagination group.', ]; diff --git a/modules/Admin/Language/sk/Person.php b/modules/Admin/Language/sk/Person.php index 5ce1e02f..9c523798 100644 --- a/modules/Admin/Language/sk/Person.php +++ b/modules/Admin/Language/sk/Person.php @@ -9,49 +9,49 @@ declare(strict_types=1); */ return [ - 'persons' => 'Persons', - 'all_persons' => 'All persons', - 'no_person' => 'Nobody found!', - 'create' => 'Create a person', - 'view' => 'View person', - 'edit' => 'Edit person', - 'delete' => 'Delete person', + 'persons' => 'Osobnosti', + 'all_persons' => 'Všetky osobnosti', + 'no_person' => 'Nikto nenájdený!', + 'create' => 'Vytvoriť osobnosť', + 'view' => 'Ukázať osobnosť', + 'edit' => 'Upraviť osobnosť', + 'delete' => 'Vymazať osobnosť', 'messages' => [ 'createSuccess' => 'Person has been successfully created!', 'editSuccess' => 'Person has been successfully updated!', - 'deleteSuccess' => 'Person has been removed!', + 'deleteSuccess' => 'Osobnosť bol/a odstránená!', ], 'form' => [ 'avatar' => 'Avatar', 'avatar_size_hint' => - 'Avatar must be squared and at least 400px wide and tall.', - 'full_name' => 'Full name', + 'Obrázok musí byť štvorcový a minimálne 400px široký a vysoký.', + 'full_name' => 'Celé meno', 'full_name_hint' => 'This is the full name or alias of the person.', - 'unique_name' => 'Unique name', - 'unique_name_hint' => 'Used for URLs', - 'information_url' => 'Information URL', + 'unique_name' => 'Unikátne meno', + 'unique_name_hint' => 'Použité pre URL odkazy', + 'information_url' => 'Informačná URL adresa', 'information_url_hint' => 'Url to a relevant resource of information about the person, such as a homepage or third-party profile platform.', - 'submit_create' => 'Create person', - 'submit_edit' => 'Save person', + 'submit_create' => 'Vytvoriť osobnosť', + 'submit_edit' => 'Uložiť osobnosť', ], 'podcast_form' => [ - 'title' => 'Manage persons', - 'add_section_title' => 'Add persons to this podcast', + 'title' => 'Spravovať osobnosti', + 'add_section_title' => 'Pridať osobnosti k tomuto podcastu', 'add_section_subtitle' => 'You may pick several persons and roles.', - 'persons' => 'Persons', + 'persons' => 'Osobnosti', 'persons_hint' => - 'You may select one or several persons with the same roles. You need to create the persons first.', - 'roles' => 'Roles', + 'Môžete vybrať jednu, alebo viac osôb s tou istou rolou. Najprv musíte osobnosti vytvoriť.', + 'roles' => 'Úlohy', 'roles_hint' => - 'You may select none, one or several roles for a person.', - 'submit_add' => 'Add person(s)', - 'remove' => 'Remove', + 'Pre osobu môžete vybrať žiadnu, jednu, alebo viac rolí.', + 'submit_add' => 'Pridať osob(y)', + 'remove' => 'Odstrániť', ], 'episode_form' => [ 'title' => 'Spravovať osobnosti', - 'add_section_title' => 'Add persons to this episode', - 'add_section_subtitle' => 'You may pick several persons and roles.', + 'add_section_title' => 'Pridať osobnosti k tejto epizóde', + 'add_section_subtitle' => 'Môžete vybrať viacero osôb a rolí.', 'persons' => 'Osobnosti', 'persons_hint' => 'Môžete vybrať jednu alebo viac osôb s tou istou rolou. Najprv by ste mali osobnosti vytvoriť.', diff --git a/modules/Admin/Language/sk/Platforms.php b/modules/Admin/Language/sk/Platforms.php index 678bdcbf..cc0bd289 100644 --- a/modules/Admin/Language/sk/Platforms.php +++ b/modules/Admin/Language/sk/Platforms.php @@ -9,13 +9,26 @@ declare(strict_types=1); */ return [ - 'title' => 'Platformy', + 'title' => [ + 'podcasting' => 'Podcastové platformy', + 'social' => 'Sociálne siete', + 'funding' => 'Funding links', + ], + 'website' => 'Webová stránka', 'home_url' => 'Prejsť na stránku {platformName}', + 'register' => 'Registrovať', 'submit_url' => 'Uverejniť podcast na platforme {platformName}', + 'your_link' => 'Váš odkaz', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', 'visible' => 'Zobraziť na úvodnej stránke podcastu?', 'on_embed' => 'Zobraziť na vnorenom prehrávači?', 'remove' => 'Remove {platformName}', - 'submit' => 'Save', + 'submit' => 'Uložiť', 'messages' => [ 'updateSuccess' => 'Platform links have been successfully updated!', 'removeLinkSuccess' => 'The platform link has been removed.', diff --git a/modules/Admin/Language/sk/Podcast.php b/modules/Admin/Language/sk/Podcast.php index 5e65da72..9e630c09 100644 --- a/modules/Admin/Language/sk/Podcast.php +++ b/modules/Admin/Language/sk/Podcast.php @@ -9,24 +9,26 @@ declare(strict_types=1); */ return [ - 'all_podcasts' => 'All podcasts', - 'no_podcast' => 'No podcast found!', - 'create' => 'Create podcast', - 'import' => 'Import podcast', + 'all_podcasts' => 'Všetky podcasty', + 'no_podcast' => 'Žiadny podcast nenájdený!', + 'create' => 'Vytvoriť podcast', + 'import' => 'Importovať podcast', + 'all_imports' => 'Podcast imports', 'new_episode' => 'Nová časť', - 'view' => 'View podcast', - 'edit' => 'Edit podcast', - 'publish' => 'Publish podcast', - 'publish_edit' => 'Edit publication', - 'delete' => 'Delete podcast', - 'see_episodes' => 'See episodes', - 'see_contributors' => 'See contributors', - 'go_to_page' => 'Go to page', + 'view' => 'Zobraziť podcast', + 'edit' => 'Upraviť podcast', + 'publish' => 'Zverejniť podcast', + 'publish_edit' => 'Upraviť zverejnené', + 'delete' => 'Vymazať podcast', + 'see_episodes' => 'Ukázať časti', + 'see_contributors' => 'Pozrieť prispievateľov', + 'monetization_other' => 'Other monetization', + 'go_to_page' => 'Prejsť na stránku', 'latest_episodes' => 'Posledné časti', 'see_all_episodes' => 'Pozrieť všetky časti', - 'draft' => 'Draft', + 'draft' => 'Koncept', 'messages' => [ - 'createSuccess' => 'Podcast successfully created!', + 'createSuccess' => 'Podcast úspešne vytvorený!', 'editSuccess' => 'Podcast has been successfully updated!', 'importSuccess' => 'Podcast has been successfully imported!', 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', @@ -47,69 +49,87 @@ return [ one {# episode was} other {# episodes were} } added to the podcast!', - 'podcastFeedUpToDate' => 'Podcast is already up to date.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', + 'podcastFeedUpToDate' => 'Podcast už je aktualizovaný.', 'publishError' => 'This podcast is either already published or scheduled for publication.', 'publishEditError' => 'This podcast is not scheduled for publication.', 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', 'scheduleDateError' => 'Schedule date must be set!', ], 'form' => [ - 'identity_section_title' => 'Podcast identity', + 'identity_section_title' => 'Identita podcastu', 'identity_section_subtitle' => 'These fields allow you to get noticed.', - 'cover' => 'Podcast cover', - 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'fediverse_section_title' => 'Identita vo Fediverse', + + 'cover' => 'Obal podcastu', + 'cover_size_hint' => 'Obrázok musí byť štvorcový a minimálne 1400px široký a vysoký.', 'banner' => 'Podcast banner', 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', 'banner_delete' => 'Delete podcast banner', - 'title' => 'Title', + 'title' => 'Názov', 'handle' => 'Handle', 'handle_hint' => 'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.', 'type' => [ - 'label' => 'Type', - 'episodic' => 'Episodic', + 'label' => 'Typ', + 'episodic' => 'Epizodický', 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', 'serial' => 'Serial', - 'serial_hint' => 'If episodes are intended to be consumed in sequential order. The oldest episodes will be presented first.', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', ], - 'description' => 'Description', - 'classification_section_title' => 'Classification', + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + ], + 'description' => 'Popis', + 'classification_section_title' => 'Zaradenie', 'classification_section_subtitle' => 'These fields will impact your audience and competition.', - 'language' => 'Language', - 'category' => 'Category', - 'category_placeholder' => 'Select a category…', - 'other_categories' => 'Other categories', + 'language' => 'Jazyk', + 'category' => 'Kategória', + 'category_placeholder' => 'Vybrať kategóriu…', + 'other_categories' => 'Ostatné kategórie', 'parental_advisory' => [ 'label' => 'Parental advisory', - 'hint' => 'Does it contain explicit content?', - 'undefined' => 'undefined', - 'clean' => 'Clean', - 'explicit' => 'Explicit', + 'hint' => 'Obsahuje explicitný obsah?', + 'undefined' => 'neuvedené', + 'clean' => 'Čistá', + 'explicit' => 'Chúlostivé', ], - 'author_section_title' => 'Author', - 'author_section_subtitle' => 'Who is managing the podcast?', - 'owner_name' => 'Owner name', + 'author_section_title' => 'Autor', + 'author_section_subtitle' => 'Kto spravuje tento podcast?', + 'owner_name' => 'Meno vlastníka', 'owner_name_hint' => 'For administrative use only. Visible in the public RSS feed.', - 'owner_email' => 'Owner email', + 'owner_email' => 'Email vlastníka', 'owner_email_hint' => 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', - 'publisher' => 'Publisher', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', + 'publisher' => 'Vydavateľ', 'publisher_hint' => 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', - 'copyright' => 'Copyright', - 'location_section_title' => 'Location', - 'location_section_subtitle' => 'What place is this podcast about?', - 'location_name' => 'Location name or address', + 'copyright' => 'Autorské práva', + 'location_section_title' => 'Umiestnenie', + 'location_section_subtitle' => 'O akom mieste/oblasti je tento podcast?', + 'location_name' => 'Názov oblasti, alebo adresa', 'location_name_hint' => 'This can be a real place or fictional', 'monetization_section_title' => 'Monetization', 'monetization_section_subtitle' => 'Earn money thanks to your audience.', - 'premium' => 'Premium', - 'premium_by_default' => 'Episodes must be set as premium by default', + 'premium' => 'Prémiový obsah', + 'premium_by_default' => 'Epizódy musia byť predvolene nastavené ako prémiové', 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', 'payment_pointer' => 'Payment Pointer for Web Monetization', 'payment_pointer_hint' => 'This is your where you will receive money thanks to Web Monetization', @@ -118,19 +138,19 @@ return [ 'If you need RSS tags that Castopod does not handle, set them here.', 'custom_rss' => 'Custom RSS tags for the podcast', 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', 'new_feed_url' => 'New feed URL', 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', - 'partnership' => 'Partnership', + 'partnership' => 'Partnerstvo', 'partner_id' => 'ID', - 'partner_link_url' => 'Link URL', - 'partner_image_url' => 'Image URL', + 'partner_link_url' => 'URL adresa odkazu', + 'partner_image_url' => 'URL adresa obrázka', 'partner_id_hint' => 'Your own partner ID', 'partner_link_url_hint' => 'The generic partner link address', 'partner_image_url_hint' => 'The generic partner image address', - 'status_section_title' => 'Status', 'block' => 'Podcast should be hidden from public catalogues', 'block_hint' => 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', @@ -138,161 +158,161 @@ return [ 'lock' => 'Prevent podcast from being copied', 'lock_hint' => 'The purpose is to tell other podcast platforms whether they are allowed to import this feed. A value of yes means that any attempt to import this feed into a new platform should be rejected.', - 'submit_create' => 'Create podcast', - 'submit_edit' => 'Save podcast', + 'submit_create' => 'Vytvoriť podcast', + 'submit_edit' => 'Uložiť podcast', ], 'category_options' => [ - 'uncategorized' => 'uncategorized', - 'arts' => 'Arts', - 'business' => 'Business', - 'comedy' => 'Comedy', - 'education' => 'Education', - 'fiction' => 'Fiction', - 'government' => 'Government', - 'health_and_fitness' => 'Health & Fitness', - 'history' => 'History', + 'uncategorized' => 'nezaradený', + 'arts' => 'Umenia', + 'business' => 'Podnikanie', + 'comedy' => 'Komédia', + 'education' => 'Vzdelanie', + 'fiction' => 'Fikcia', + 'government' => 'Štátna správa', + 'health_and_fitness' => 'Zdravie a fitnes', + 'history' => 'História', 'kids_and_family' => 'Kids & Family', - 'leisure' => 'Leisure', - 'music' => 'Music', - 'news' => 'News', + 'leisure' => 'Voľný čas', + 'music' => 'Hudba', + 'news' => 'Správy', 'religion_and_spirituality' => 'Religion & Spirituality', - 'science' => 'Science', - 'society_and_culture' => 'Society & Culture', - 'sports' => 'Sports', - 'technology' => 'Technology', - 'true_crime' => 'True Crime', + 'science' => 'Veda', + 'society_and_culture' => 'Spoločnosť a kultúra', + 'sports' => 'Športy', + 'technology' => 'Technológia', + 'true_crime' => 'Skutočné krimi', 'tv_and_film' => 'TV & Film', - 'books' => 'Books', - 'design' => 'Design', + 'books' => 'Knihy', + 'design' => 'Dizajn', 'fashion_and_beauty' => 'Fashion & Beauty', - 'food' => 'Food', - 'performing_arts' => 'Performing Arts', - 'visual_arts' => 'Visual Arts', - 'careers' => 'Careers', - 'entrepreneurship' => 'Entrepreneurship', - 'investing' => 'Investing', - 'management' => 'Management', + 'food' => 'Jedlo', + 'performing_arts' => 'Divadelné umenie', + 'visual_arts' => 'Vizuálni umelci', + 'careers' => 'Kariéra', + 'entrepreneurship' => 'Podnikateľský', + 'investing' => 'Investičný', + 'management' => 'Manažment', 'marketing' => 'Marketing', - 'non_profit' => 'Non-Profit', - 'comedy_interviews' => 'Comedy Interviews', + 'non_profit' => 'Neziskový', + 'comedy_interviews' => 'Komediálne rozhovory', 'improv' => 'Improv', 'stand_up' => 'Stand-Up', - 'courses' => 'Courses', - 'how_to' => 'How To', - 'language_learning' => 'Language Learning', - 'self_improvement' => 'Self-Improvement', + 'courses' => 'Kurzy', + 'how_to' => 'Ako na to', + 'language_learning' => 'Učenie jazykov', + 'self_improvement' => 'Sebazdokonaľovanie', 'comedy_fiction' => 'Comedy Fiction', - 'drama' => 'Drama', - 'science_fiction' => 'Science Fiction', + 'drama' => 'Dráma', + 'science_fiction' => 'Vedecko-fantastické', 'alternative_health' => 'Alternative Health', 'fitness' => 'Fitness', - 'medicine' => 'Medicine', - 'mental_health' => 'Mental Health', + 'medicine' => 'Medicínsky', + 'mental_health' => 'Duševné zdravie', 'nutrition' => 'Nutrition', - 'sexuality' => 'Sexuality', - 'education_for_kids' => 'Education for Kids', - 'parenting' => 'Parenting', + 'sexuality' => 'Sexualita', + 'education_for_kids' => 'Vzdelávanie pre deti', + 'parenting' => 'Rodičovstvo', 'pets_and_animals' => 'Pets & Animals', - 'stories_for_kids' => 'Stories for Kids', + 'stories_for_kids' => 'Príbehy pre deti', 'animation_and_manga' => 'Animation & Manga', 'automotive' => 'Automotive', 'aviation' => 'Aviation', 'crafts' => 'Crafts', - 'games' => 'Games', - 'hobbies' => 'Hobbies', + 'games' => 'Hry', + 'hobbies' => 'Záľuby', 'home_and_garden' => 'Home & Garden', - 'video_games' => 'Video Games', + 'video_games' => 'Videohry', 'music_commentary' => 'Music Commentary', - 'music_history' => 'Music History', - 'music_interviews' => 'Music Interviews', + 'music_history' => 'Hudobná história', + 'music_interviews' => 'Hudobné rozhovory', 'business_news' => 'Business News', - 'daily_news' => 'Daily News', + 'daily_news' => 'Denné správy', 'entertainment_news' => 'Entertainment News', 'news_commentary' => 'News Commentary', - 'politics' => 'Politics', - 'sports_news' => 'Sports News', - 'tech_news' => 'Tech News', + 'politics' => 'Politika', + 'sports_news' => 'Športové správy', + 'tech_news' => 'Technologické novinky', 'buddhism' => 'Buddhism', - 'christianity' => 'Christianity', - 'hinduism' => 'Hinduism', + 'christianity' => 'Kresťanstvo', + 'hinduism' => 'Hinduizmus', 'islam' => 'Islam', 'judaism' => 'Judaism', - 'religion' => 'Religion', - 'spirituality' => 'Spirituality', - 'astronomy' => 'Astronomy', - 'chemistry' => 'Chemistry', + 'religion' => 'Náboženstvo', + 'spirituality' => 'Duchovno', + 'astronomy' => 'Astronómia', + 'chemistry' => 'Chémia', 'earth_sciences' => 'Earth Sciences', 'life_sciences' => 'Life Sciences', - 'mathematics' => 'Mathematics', + 'mathematics' => 'Matematické', 'natural_sciences' => 'Natural Sciences', - 'nature' => 'Nature', - 'physics' => 'Physics', - 'social_sciences' => 'Social Sciences', - 'documentary' => 'Documentary', + 'nature' => 'Príroda', + 'physics' => 'Fyzika', + 'social_sciences' => 'Sociálne vedy', + 'documentary' => 'Dokumentárny', 'personal_journals' => 'Personal Journals', - 'philosophy' => 'Philosophy', + 'philosophy' => 'Filozofia', 'places_and_travel' => 'Places & Travel', - 'relationships' => 'Relationships', - 'baseball' => 'Baseball', + 'relationships' => 'Vzťahy', + 'baseball' => 'Bejzbal', 'basketball' => 'Basketball', 'cricket' => 'Cricket', 'fantasy_sports' => 'Fantasy Sports', - 'football' => 'Football', + 'football' => 'Futbal', 'golf' => 'Golf', 'hockey' => 'Hockey', 'rugby' => 'Rugby', 'running' => 'Running', - 'soccer' => 'Soccer', - 'swimming' => 'Swimming', - 'tennis' => 'Tennis', - 'volleyball' => 'Volleyball', - 'wilderness' => 'Wilderness', - 'wrestling' => 'Wrestling', + 'soccer' => 'Futbal', + 'swimming' => 'Plávanie', + 'tennis' => 'Tenis', + 'volleyball' => 'Volejbal', + 'wilderness' => 'Divočina', + 'wrestling' => 'Zápasnícky', 'after_shows' => 'After Shows', - 'film_history' => 'Film History', - 'film_interviews' => 'Film Interviews', - 'film_reviews' => 'Film Reviews', - 'tv_reviews' => 'TV Reviews', + 'film_history' => 'Filmová história', + 'film_interviews' => 'Filmové rozhovory', + 'film_reviews' => 'Filmové recenzie', + 'tv_reviews' => 'TV recenzie', ], 'publish_form' => [ - 'back_to_podcast_dashboard' => 'Back to podcast dashboard', - 'post' => 'Your announcement post', + 'back_to_podcast_dashboard' => 'Späť na podcastovú nástenku', + 'post' => 'Váš oznamovací príspevok', 'post_hint' => "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", - 'message_placeholder' => 'Write your message…', - 'submit' => 'Publish', - 'publication_date' => 'Publication date', + 'message_placeholder' => 'Napíšte vašu správu…', + 'submit' => 'Zverejniť', + 'publication_date' => 'Dátum zverejnenia', 'publication_method' => [ - 'now' => 'Now', - 'schedule' => 'Schedule', + 'now' => 'Hneď teraz', + 'schedule' => 'Naplánovať', ], - 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date' => 'Dátum plánovaného zverejnenia', 'scheduled_publication_date_hint' => 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', - 'submit_edit' => 'Edit publication', - 'cancel_publication' => 'Cancel publication', + 'submit_edit' => 'Upraviť zverejnenie', + 'cancel_publication' => 'Zrušiť zverejnenie', 'message_warning' => 'You did not write a message for your announcement post!', 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', - 'message_warning_submit' => 'Publish anyway', + 'message_warning_submit' => 'Napriek tomu zverejniť', ], 'publication_status_banner' => [ - 'draft_mode' => 'draft mode', - 'not_published' => 'This podcast is not yet published.', + 'draft_mode' => 'konceptový režim', + 'not_published' => 'Tento podcast ešte nieje zverejnený.', 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', ], 'delete_form' => [ 'disclaimer' => "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", 'understand' => 'I understand, I want the podcast to be permanently deleted', - 'submit' => 'Delete', + 'submit' => 'Vymazať', ], - 'by' => 'By {publisher}', + 'by' => 'Od {publisher}', 'season' => 'Season {seasonNumber}', 'list_of_episodes_year' => '{year} episodes ({episodeCount})', 'list_of_episodes_season' => 'Season {seasonNumber} episodes ({episodeCount})', - 'no_episode' => 'No episode found!', - 'follow' => 'Follow', + 'no_episode' => 'Žiadna epizóda nenájdená!', + 'follow' => 'Nasledovať', 'followers' => '{numberOfFollowers, plural, one {# follower} other {# followers} @@ -301,10 +321,10 @@ return [ one {# post} other {# posts} }', - 'activity' => 'Activity', - 'episodes' => 'Episodes', - 'sponsor' => 'Sponsor', - 'funding_links' => 'Funding links for {podcastTitle}', - 'find_on' => 'Find {podcastTitle} on', - 'listen_on' => 'Listen on', + 'activity' => 'Aktivita', + 'episodes' => 'Časti', + 'sponsor' => 'Sponzor', + 'funding_links' => 'Odkazy na financovanie {podcastTitle}', + 'find_on' => 'Nájsť {podcastTitle} na', + 'listen_on' => 'Počúvajte na', ]; diff --git a/modules/Admin/Language/sk/PodcastImport.php b/modules/Admin/Language/sk/PodcastImport.php deleted file mode 100644 index 7c3ef67d..00000000 --- a/modules/Admin/Language/sk/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'The podcast to import', - 'old_podcast_section_subtitle' => - 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', - 'imported_feed_url' => 'Feed URL', - 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', - 'new_podcast_section_title' => 'The new podcast', - 'advanced_params_section_title' => 'Advanced parameters', - 'advanced_params_section_subtitle' => - 'Keep the default values if you have no idea of what the fields are for.', - 'slug_field' => 'Field to be used to calculate episode slug', - 'description_field' => - 'Source field used for episode description / show notes', - 'force_renumber' => 'Force episodes renumbering', - 'force_renumber_hint' => - 'Use this if your podcast does not have episode numbers but wish to set them during import.', - 'season_number' => 'Season number', - 'season_number_hint' => - 'Use this if your podcast does not have a season number but wish to set one during import. Leave blank otherwise.', - 'max_episodes' => 'Maximum number of episodes to import', - 'max_episodes_hint' => 'Leave blank to import all episodes', - 'lock_import' => - 'This feed is protected. You cannot import it. If you are the owner, unprotect it on the origin platform.', - 'submit' => 'Import podcast', -]; diff --git a/modules/Admin/Language/sk/PodcastNavigation.php b/modules/Admin/Language/sk/PodcastNavigation.php index b4d7ddc0..f90c67a2 100644 --- a/modules/Admin/Language/sk/PodcastNavigation.php +++ b/modules/Admin/Language/sk/PodcastNavigation.php @@ -9,30 +9,34 @@ declare(strict_types=1); */ return [ - 'go_to_page' => 'Go to podcast page', + 'go_to_page' => 'Ísť na stránku podcastu', + 'rss_feed' => 'RSS kanál', 'dashboard' => 'Podcast dashboard', - 'podcast-view' => 'Home', - 'podcast-edit' => 'Edit podcast', + 'podcast-view' => 'Úvod', + 'podcast-edit' => 'Upraviť podcast', 'podcast-persons-manage' => 'Manage persons', - 'episodes' => 'Episodes', - 'episode-list' => 'All episodes', - 'episode-create' => 'New episode', + 'podcast-imports' => 'Nahrané podcasty', + 'podcast-imports-sync' => 'Sync feeds', + 'episodes' => 'Časti', + 'episode-list' => 'Všetky časti', + 'episode-create' => 'Nová časť', 'analytics' => 'Analytics', 'podcast-analytics' => 'Audience overview', 'podcast-analytics-webpages' => 'Web pages visits', 'podcast-analytics-locations' => 'Locations', 'podcast-analytics-unique-listeners' => 'Unique listeners', - 'podcast-analytics-players' => 'Players', - 'podcast-analytics-listening-time' => 'Listening time', - 'podcast-analytics-time-periods' => 'Time periods', - 'premium' => 'Premium', - 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', - 'contributors' => 'Contributors', - 'contributor-list' => 'All contributors', - 'contributor-add' => 'Add contributor', - 'platforms' => 'External platforms', - 'platforms-podcasting' => 'Podcasting', - 'platforms-social' => 'Social networks', - 'platforms-funding' => 'Funding', + 'podcast-analytics-players' => 'Prehrávače', + 'podcast-analytics-listening-time' => 'Čas počúvania', + 'podcast-analytics-time-periods' => 'Časové úseky', + 'monetization' => 'Monetization', + 'subscription-list' => 'Všetky odbery', + 'subscription-create' => 'Pridať odber', + 'contributors' => 'Prispievatelia', + 'contributor-list' => 'Všetci prispievatelia', + 'contributor-add' => 'Pridať prispievateľa', + 'broadcast' => 'Vysielanie', + 'platforms-podcasting' => 'Podcastové aplikácie', + 'platforms-social' => 'Sociálne siete', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', ]; diff --git a/modules/Admin/Language/sk/Settings.php b/modules/Admin/Language/sk/Settings.php index 4a70dcba..63441b6a 100644 --- a/modules/Admin/Language/sk/Settings.php +++ b/modules/Admin/Language/sk/Settings.php @@ -9,21 +9,21 @@ declare(strict_types=1); */ return [ - 'title' => 'General settings', + 'title' => 'Všeobecné nastavenia', 'instance' => [ - 'title' => 'Instance', - 'site_icon' => 'Site icon', - 'site_icon_delete' => 'Delete site icon', + 'title' => 'Inštancia', + 'site_icon' => 'Ikona stránky', + 'site_icon_delete' => 'Odstrániť ikonu stránky', 'site_icon_hint' => 'Site icons are what you see on your browser tabs, bookmarks bar, and when you add a website as a shortcut on mobile devices.', 'site_icon_helper' => 'Icon must be squared and at least 512px wide and tall.', - 'site_name' => 'Site name', - 'site_description' => 'Site description', - 'submit' => 'Save', + 'site_name' => 'Názov stránky', + 'site_description' => 'Popis stránky', + 'submit' => 'Uložiť', 'editSuccess' => 'Instance has been updated successfully!', 'deleteIconSuccess' => 'Site icon has been remove successfully!', ], 'images' => [ - 'title' => 'Images', + 'title' => 'Obrázky', 'subtitle' => 'Here you can regenerate all images based on the originals that were uploaded. To be used if you find that some images are missing. This task may take a while.', 'regenerate' => 'Regenerate images', 'regenerationSuccess' => 'All images have been regenerated successfully!', @@ -43,16 +43,16 @@ return [ 'runSuccess' => 'Housekeeping has been run successfully!', ], 'theme' => [ - 'title' => 'Theme', + 'title' => 'Vzhľad', 'accent_section_title' => 'Accent color', 'accent_section_subtitle' => 'Choose the color to determine the look and feel of all public pages.', 'pine' => 'Pine', - 'crimson' => 'Crimson', + 'crimson' => 'Karmínová', 'amber' => 'Amber', 'lake' => 'Lake', 'jacaranda' => 'Jacaranda', 'onyx' => 'Onyx', - 'submit' => 'Save', + 'submit' => 'Uložiť', 'setInstanceThemeSuccess' => 'Theme has been updated successfully!', ], ]; diff --git a/modules/Admin/Language/sk/Soundbite.php b/modules/Admin/Language/sk/Soundbite.php index a3f828fe..6b118afb 100644 --- a/modules/Admin/Language/sk/Soundbite.php +++ b/modules/Admin/Language/sk/Soundbite.php @@ -10,8 +10,8 @@ declare(strict_types=1); return [ 'list' => [ - 'title' => 'Soundbites', - 'soundbite' => 'Soundbite', + 'title' => 'Zvukové ukážky', + 'soundbite' => 'Zvuková ukážka', ], 'messages' => [ 'createSuccess' => 'Soundbite has been successfully created!', @@ -19,13 +19,13 @@ return [ ], 'form' => [ 'title' => 'New soundbite', - 'soundbite_title' => 'Soundbite title', - 'start_time' => 'Start at', - 'duration' => 'Duration', - 'submit' => 'Create soundbite', + 'soundbite_title' => 'Názov zvukovej ukážky', + 'start_time' => 'Začiatok na', + 'duration' => 'Trvanie', + 'submit' => 'Vytvoriť zvukovú ukážku', ], 'play' => 'Play soundbite', 'stop' => 'Stop soundbite', 'create' => 'New soundbite', - 'delete' => 'Delete soundbite', + 'delete' => 'Vymazať zvukovú ukážku', ]; diff --git a/modules/Admin/Language/sk/User.php b/modules/Admin/Language/sk/User.php deleted file mode 100644 index 585d6799..00000000 --- a/modules/Admin/Language/sk/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', - 'ban' => 'Ban', - 'unban' => 'Unban', - 'delete' => 'Delete', - 'create' => 'New user', - 'view' => "{username}'s info", - 'all_users' => 'All users', - 'list' => [ - 'user' => 'User', - 'roles' => 'Roles', - 'banned' => 'Banned?', - ], - 'form' => [ - 'email' => 'Email', - 'username' => 'Username', - 'password' => 'Password', - 'new_password' => 'New Password', - 'roles' => 'Roles', - 'permissions' => 'Permissions', - 'submit_create' => 'Create user', - 'submit_edit' => 'Save', - 'submit_password_change' => 'Change!', - ], - 'roles' => [ - 'superadmin' => 'Super admin', - ], - 'messages' => [ - 'createSuccess' => - 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => - "{username}'s roles have been successfully updated.", - 'forcePassResetSuccess' => - '{username} will be prompted with a password reset upon next visit.', - 'banSuccess' => '{username} has been banned.', - 'unbanSuccess' => '{username} has been unbanned.', - 'editOwnerError' => - '{username} is the instance owner, you cannot edit its roles.', - 'banSuperAdminError' => - '{username} is a superadmin, one does not simply ban a superadmin…', - 'deleteSuperAdminError' => - '{username} is a superadmin, one does not simply delete a superadmin…', - 'deleteSuccess' => '{username} has been deleted.', - ], -]; diff --git a/modules/Admin/Language/sk/Validation.php b/modules/Admin/Language/sk/Validation.php index 750b1968..f76c3163 100644 --- a/modules/Admin/Language/sk/Validation.php +++ b/modules/Admin/Language/sk/Validation.php @@ -13,6 +13,5 @@ return [ '{field} is either not an image, or it is not wide or tall enough.', 'is_image_ratio' => '{field} is either not an image or not of the right ratio.', - 'validate_url' => - 'The {field} field must be a valid URL (eg. https://example.com/).', + 'is_json' => '{field} contains invalid JSON.', ]; diff --git a/modules/Admin/Language/sk/VideoClip.php b/modules/Admin/Language/sk/VideoClip.php index 638de697..cfe35050 100644 --- a/modules/Admin/Language/sk/VideoClip.php +++ b/modules/Admin/Language/sk/VideoClip.php @@ -12,10 +12,10 @@ return [ 'list' => [ 'title' => 'Video clips', 'status' => [ - 'label' => 'Status', - 'queued' => 'queued', + 'label' => 'Stav', + 'queued' => 'v poradí', 'queued_hint' => 'Clip is waiting to be processed.', - 'pending' => 'pending', + 'pending' => 'čaká', 'pending_hint' => 'Clip will be generated shortly.', 'running' => 'running', 'running_hint' => 'Clip is being generated.', @@ -55,8 +55,8 @@ return [ 'squared_hint' => 'With a 1:1 ratio, squared videos are great for Mastodon, Facebook, Twitter and LinkedIn.', ], 'theme' => 'Select a theme', - 'start_time' => 'Start at', - 'duration' => 'Duration', + 'start_time' => 'Začiatok na', + 'duration' => 'Trvanie', 'trim_start' => 'Trim start', 'trim_end' => 'Trim end', 'submit' => 'Create video clip', diff --git a/modules/Admin/Language/sr-latn/AboutCastopod.php b/modules/Admin/Language/sr-latn/AboutCastopod.php new file mode 100644 index 00000000..66ccc2e3 --- /dev/null +++ b/modules/Admin/Language/sr-latn/AboutCastopod.php @@ -0,0 +1,22 @@ + 'O Castopod-u', + 'host_name' => 'Naziv hosta', + 'version' => 'Verzija Castopod-a', + 'php_version' => 'PHP verzija', + 'os' => 'Operativni Sistem', + 'languages' => 'Jezici', + 'update_database' => 'Ažuriraj baze podataka', + 'messages' => [ + 'databaseUpdateSuccess' => 'Baza podataka je ažurirana!', + ], +]; diff --git a/modules/Admin/Language/sr-latn/Breadcrumb.php b/modules/Admin/Language/sr-latn/Breadcrumb.php new file mode 100644 index 00000000..3376dedd --- /dev/null +++ b/modules/Admin/Language/sr-latn/Breadcrumb.php @@ -0,0 +1,57 @@ + 'breadcrumb polja', + config('Admin') + ->gateway => 'Početna', + 'podcasts' => 'podkasti', + 'episodes' => 'epizode', + 'subscriptions' => 'pretplate', + 'contributors' => 'saradnici', + 'pages' => 'stranice', + 'settings' => 'podešavanja', + 'theme' => 'tema', + 'about' => 'osnovni podaci', + 'add' => 'dodaj', + 'new' => 'nov', + 'edit' => 'izmeni', + 'persons' => 'osobe', + 'publish' => 'objavi', + 'publish-edit' => 'uredi objavu', + 'publish-date-edit' => 'uredi datum objave', + 'unpublish' => 'ukolni objavu', + 'delete' => 'obriši', + 'remove' => 'ukloni', + 'fediverse' => 'fediverse', + 'blocked-actors' => 'blokirani nalozi', + 'blocked-domains' => 'blokirani domeni', + 'users' => 'korisnici', + 'my-account' => 'moj nalog', + 'change-password' => 'promenite lozinku', + 'imports' => 'uvozi', + 'sync-feeds' => 'sinhronizuj snabdevače', + 'platforms' => 'platforme', + 'social' => 'društvene mreže', + 'funding' => 'finansiranje', + 'monetization-other' => 'druga monetizacija', + 'analytics' => 'analitika', + 'locations' => 'lokacije', + 'webpages' => 'veb strane', + 'unique-listeners' => 'jedinstveni slušaoci', + 'players' => 'plejeri', + 'listening-time' => 'ukupno vreme slušanja', + 'time-periods' => 'vremenski periodi', + 'soundbites' => 'zvučni isečci', + 'video-clips' => 'video isečci', + 'embed' => 'embedovan plejer', + 'notifications' => 'obaveštenja', + 'suspend' => 'obustavi', +]; diff --git a/modules/Admin/Language/sr-latn/Charts.php b/modules/Admin/Language/sr-latn/Charts.php new file mode 100644 index 00000000..cb90b13b --- /dev/null +++ b/modules/Admin/Language/sr-latn/Charts.php @@ -0,0 +1,41 @@ + 'Preuzimanja epizode po servisu (za prošlu nedelju)', + 'by_player_weekly' => 'Preuzimanja epizode po plejeru (za prošlu nedelju)', + 'by_player_yearly' => 'Preuzimanja epizode po plejeru (za prošlu godinu)', + 'by_device_weekly' => 'Preuzimanja epizode po uređaju (za prošlu nedelju)', + 'by_os_weekly' => 'Preuzimanja epizode po operativnom sistemu (za prošlu nedelju)', + 'podcast_by_region' => 'Preuzimanja epizode po regionu (za prošlu nedelju)', + 'unique_daily_listeners' => 'Svakodnevni jedinstveni slušaoci', + 'unique_monthly_listeners' => 'Mesečni jedinstveni slušaoci', + 'by_browser' => 'Korišćenje veb stranica od strane pretraživača (za prošlu nedelju)', + 'podcast_by_day' => 'Dnevno preuzimanje epizoda', + 'podcast_by_month' => 'Mesečno preuzimanje epizoda', + 'episode_by_day' => 'Dnevno preuzimanje epizoda (prvih 60 dana)', + 'episode_by_month' => 'Mesečno preuzimanje epizoda', + 'episodes_by_day' => + 'Preuzimanja poslednjih 5 epizoda (u prvih 60 dana)', + 'by_country_weekly' => 'Preuzimanja epizode po državi (za prošlu nedelju)', + 'by_country_yearly' => 'Preuzimanja epizode po državi (za prošlu godinu)', + 'by_domain_weekly' => 'Posete veb stranicama prema izvoru (za prošlu nedelju)', + 'by_domain_yearly' => 'Posete veb stranicama prema izvoru (za prošlu godinu)', + 'by_entry_page' => 'Posete veb stranicama prema odredišnoj stranici (za prošlu nedelju)', + 'podcast_bots' => 'Botovi (pokretači)', + 'daily_listening_time' => 'Dnevno kumulativno vreme slušanja', + 'monthly_listening_time' => 'Mesečno kumulativno vreme slušanja', + 'by_weekday' => 'Po danu u nedelji (za poslednjih 60 dana)', + 'by_hour' => 'Po dobu dana (za poslednjih 60 dana)', + 'podcast_by_bandwidth' => 'Dnevno korišćen protok (u MB)', + 'total_storage_by_month' => 'Mesečni skladišni prostor (u MB)', + 'total_bandwidth_by_month' => 'Mesečno korišćen protok (u MB)', + 'total_bandwidth_by_month_limit' => 'Ograničeno na {totalBandwidth} mesečno', +]; diff --git a/modules/Admin/Language/sr-latn/Common.php b/modules/Admin/Language/sr-latn/Common.php new file mode 100644 index 00000000..8fc6f706 --- /dev/null +++ b/modules/Admin/Language/sr-latn/Common.php @@ -0,0 +1,52 @@ + 'Da', + 'no' => 'Ne', + 'cancel' => 'Otkaži', + 'optional' => 'Opciono', + 'more' => 'Više', + 'no_data' => 'Nema pronađenih podataka!', + 'close' => 'Zatvori', + 'edit' => 'Izmeni', + 'copy' => 'Kopiraj', + 'copied' => 'Kopirano!', + 'home' => 'Početna stranica', + 'explicit' => 'Eksplicitno', + 'powered_by' => 'Pokreće {castopod}', + 'actions' => 'Akcije', + 'pageInfo' => 'Stranica {currentPage} od {pageCount}', + 'go_back' => 'Nazad', + 'forms' => [ + 'editor' => [ + 'write' => 'Piši', + 'preview' => 'Pregled', + 'help' => 'Pokreće markdown', + ], + 'multiSelect' => [ + 'selectText' => 'Pritisni da odabereš', + 'loadingText' => 'Učitavanje…', + 'noResultsText' => 'Nije pronađen nijedan rezultat', + 'noChoicesText' => 'Nema opcija koje je moguće odabrati', + 'maxItemText' => 'Nije moguće dodati više stavki', + ], + 'upload_file' => 'Otpremite datoteku', + 'remote_url' => 'Daljinski URL', + 'save' => 'Sačuvaj', + ], + 'play_episode_button' => [ + 'play' => 'Pusti', + 'playing' => 'Reprodukujem', + ], + 'size_limit' => 'Limit veličine: {0}.', + 'choose_interact' => 'Odaberite način interakcije', + 'view' => 'Pogledaj', +]; diff --git a/modules/Admin/Language/sr-latn/Countries.php b/modules/Admin/Language/sr-latn/Countries.php new file mode 100644 index 00000000..64b97465 --- /dev/null +++ b/modules/Admin/Language/sr-latn/Countries.php @@ -0,0 +1,264 @@ + 'Andora', + 'AE' => 'Ujedinjeni Arapski Emirati', + 'AF' => 'Afganistan', + 'AG' => 'Antiga i Barbuda', + 'AI' => 'Angvila', + 'AL' => 'Albanija', + 'AM' => 'Jermenija', + 'AO' => 'Angola', + 'AQ' => 'Antarktik', + 'AR' => 'Argentina', + 'AS' => 'Američka Samoa', + 'AT' => 'Austrija', + 'AU' => 'Australija', + 'AW' => 'Aruba', + 'AX' => 'Olandska Ostrva', + 'AZ' => 'Azerbejdžan', + 'BA' => 'Bosna i Hercegovina', + 'BB' => 'Barbados', + 'BD' => 'Bangladeš', + 'BE' => 'Bеlgija', + 'BF' => 'Burkina Faso', + 'BG' => 'Bugarska', + 'BH' => 'Bahrеin', + 'BI' => 'Burundi', + 'BJ' => 'Benin', + 'BL' => 'Sveti Bartolomej', + 'BM' => 'Bermuda', + 'BN' => 'Bruneji Darusalam', + 'BO' => 'Bolivija, Višenacionalna Država', + 'BQ' => 'Bonеr, Svеti Eustahijе i Saba', + 'BR' => 'Brazil', + 'BS' => 'Bahami', + 'BT' => 'Butan', + 'BV' => 'Ostrvo Buve', + 'BW' => 'Bocvana', + 'BY' => 'Bеlorusija', + 'BZ' => 'Belize', + 'CA' => 'Kanada', + 'CC' => 'Kokosova (Kilingova) Ostrva', + 'CD' => 'Demokratska Republika Kongo', + 'CF' => 'Centralnoafrička Republika', + 'CG' => 'Kongo', + 'CH' => 'Švajcarska', + 'CI' => "Obala Slonovače", + 'CK' => 'Kukova ostrva', + 'CL' => 'Čilе', + 'CM' => 'Kamerun', + 'CN' => 'Kina', + 'CO' => 'Kolumbija', + 'CR' => 'Kostarika', + 'CU' => 'Kuba', + 'CV' => 'Zelenortska Ostrva', + 'CW' => 'Kurasao', + 'CX' => 'Uskršnja ostrva', + 'CY' => 'Kipar', + 'CZ' => 'Češka Republika', + 'DE' => 'Nemačka', + 'DJ' => 'Džibuti', + 'DK' => 'Danska', + 'DM' => 'Dominika', + 'DO' => 'Dominikanska Republika', + 'DZ' => 'Alžir', + 'EC' => 'Ekvador', + 'EE' => 'Estonija', + 'EG' => 'Egipat', + 'EH' => 'Zapadna Sahara', + 'ER' => 'Eritreja', + 'ES' => 'Španija', + 'ET' => 'Etiopija', + 'FI' => 'Finska', + 'FJ' => 'Fidži', + 'FK' => 'Foklandska ostrva (Malvini)', + 'FM' => 'Savezne države Mikronezije', + 'FO' => 'Farska Ostrva', + 'FR' => 'Francuska', + 'GA' => 'Gabon', + 'GB' => 'Ujedinjeno Kraljevstvo', + 'GD' => 'Grenada', + 'GE' => 'Gruzija', + 'GF' => 'Francuska Gvajana', + 'GG' => 'Gernzi', + 'GH' => 'Gana', + 'GI' => 'Gibraltar', + 'GL' => 'Grеnland', + 'GM' => 'Gambija', + 'GN' => 'Gvineja', + 'GP' => 'Gvadelupe', + 'GQ' => 'Ekvatorijalna Gvineja', + 'GR' => 'Grčka', + 'GS' => 'Južna Džordžija i Južna Sendvič Ostrva', + 'GT' => 'Gvatemala', + 'GU' => 'Guam', + 'GW' => 'Gvineja-Bisao', + 'GY' => 'Gvajana', + 'HK' => 'Hong Kong', + 'HM' => 'Ostrva Herd i Makdonald', + 'HN' => 'Honduras', + 'HR' => 'Hrvatska', + 'HT' => 'Haiti', + 'HU' => 'Mađarska', + 'ID' => 'Indonezija', + 'IE' => 'Irska', + 'IL' => 'Izrael', + 'IM' => 'Ostrvo Man', + 'IN' => 'Indija', + 'IO' => 'Britanska Territorija u Indijskom Okeanu', + 'IQ' => 'Irak', + 'IR' => 'Islamska Republika Iran', + 'IS' => 'Island', + 'IT' => 'Italija', + 'JE' => 'Džersi', + 'JM' => 'Jamajka', + 'JO' => 'Jordan', + 'JP' => 'Japan', + 'KE' => 'Kenija', + 'KG' => 'Kirgistan', + 'KH' => 'Kambodža', + 'KI' => 'Kiribati', + 'KM' => 'Komorska Ostrva', + 'KN' => 'Sveti Kits i Nevis', + 'KP' => "Demokratska Narodna Republika Koreja", + 'KR' => 'Republika Koreja', + 'KW' => 'Kuvajt', + 'KY' => 'Kajmanska ostrva', + 'KZ' => 'Kazahstan', + 'LA' => "Laos", + 'LB' => 'Liban', + 'LC' => 'Sveta Lucija', + 'LI' => 'Lihtenštajn', + 'LK' => 'Šri Lanka', + 'LR' => 'Liberija', + 'LS' => 'Lesoto', + 'LT' => 'Litvanija', + 'LU' => 'Luksemburg', + 'LV' => 'Letonija', + 'LY' => 'Libija', + 'MA' => 'Maroko', + 'MC' => 'Monako', + 'MD' => 'Republika Moldavija', + 'ME' => 'Crna Gora', + 'MF' => 'Sveti Martin (Francuski deo)', + 'MG' => 'Madagaskar', + 'MH' => 'Maršalska Ostrva', + 'MK' => 'Severna Makedonija', + 'ML' => 'Mali', + 'MM' => 'Mijanmar', + 'MN' => 'Mongolija', + 'MO' => 'Makao', + 'MP' => 'Severna Marijanska Ostrva', + 'MQ' => 'Martinik', + 'MR' => 'Mauritanija', + 'MS' => 'Montserat', + 'MT' => 'Malta', + 'MU' => 'Mauricijus', + 'MV' => 'Maldivi', + 'MW' => 'Malavi', + 'MX' => 'Meksiko', + 'MY' => 'Malezija', + 'MZ' => 'Mozambik', + 'N/A' => 'Nije primenjivo (Lokalna IP…)', + 'NA' => 'Namibija', + 'NC' => 'Nova Kaledonija', + 'NE' => 'Niger', + 'NF' => 'Ostrvo Norfolk', + 'NG' => 'Nigerija', + 'NI' => 'Nikaragva', + 'NL' => 'Holandija', + 'NO' => 'Norveška', + 'NP' => 'Nepal', + 'NR' => 'Nauru', + 'NU' => 'Nijue', + 'NZ' => 'Novi Zeland', + 'OM' => 'Oman', + 'PA' => 'Panama', + 'PE' => 'Peru', + 'PF' => 'Francuska Polinezija', + 'PG' => 'Papua Nova Gvinеja', + 'PH' => 'Filipini', + 'PK' => 'Pakistan', + 'PL' => 'Poljska', + 'PM' => 'Sen Pjer i Mikelon', + 'PN' => 'Pitkern', + 'PR' => 'Portoriko', + 'PS' => 'Palestina', + 'PT' => 'Portugalija', + 'PW' => 'Palau', + 'PY' => 'Paragvaj', + 'QA' => 'Katar', + 'RE' => 'Rejunion', + 'RO' => 'Rumunija', + 'RS' => 'Srbija', + 'RU' => 'Ruska Federacija', + 'RW' => 'Ruanda', + 'SA' => 'Saudijska Arabija', + 'SB' => 'Solomonova ostrva', + 'SC' => 'Sejšeli', + 'SD' => 'Sudan', + 'SE' => 'Švedska', + 'SG' => 'Singapur', + 'SH' => 'Sveta Helena, Asension i Tristan da Kunja', + 'SI' => 'Slovеnija', + 'SJ' => 'Svalbard i Jan Majen', + 'SK' => 'Slovačka', + 'SL' => 'Sijera Leone', + 'SM' => 'San Marino', + 'SN' => 'Senegal', + 'SO' => 'Somalija', + 'SR' => 'Surinam', + 'SS' => 'Južni Sudan', + 'ST' => 'Sao Tomе i Prinsipе', + 'SV' => 'Salvador', + 'SX' => 'Sveti Martin (Holandski deo)', + 'SY' => 'Sirijska Arapska Republika', + 'SZ' => 'Svazilеnd', + 'TC' => 'Tеrks i Kеjkos', + 'TD' => 'Čad', + 'TF' => 'Francuske Južne Teritorije', + 'TG' => 'Togo', + 'TH' => 'Таjland', + 'TJ' => 'Tadžikistan', + 'TK' => 'Tokelau', + 'TL' => 'Istočni Timor', + 'TM' => 'Turkmenistan', + 'TN' => 'Tunis', + 'TO' => 'Tonga', + 'TR' => 'Turska', + 'TT' => 'Trinidad i Tobago', + 'TV' => 'Tuvalu', + 'TW' => 'Tajvan, Kineska provincija', + 'TZ' => 'Ujedinjena Republika Tanzanija', + 'UA' => 'Ukrajina', + 'UG' => 'Uganda', + 'UM' => 'Mala spoljna ostrva Sjedinjenih Američkih Država', + 'US' => 'Sjеdinjеnе Državе', + 'UY' => 'Urugvaj', + 'UZ' => 'Uzbekistan', + 'VA' => 'Sveta stolica (Grad-država Vatikan)', + 'VC' => 'Sveti Vinsent i Grenadini', + 'VE' => 'Venecuela', + 'VG' => 'Devičanska ostrva', + 'VI' => 'Devičanska Ostrva, S.A.D.', + 'VN' => 'Vijetnam', + 'VU' => 'Vanuatu', + 'WF' => 'Valis i Futuna', + 'WS' => 'Samoa', + 'YE' => 'Jemen', + 'YT' => 'Majote', + 'ZA' => 'Južna Afrika', + 'ZM' => 'Zambija', + 'ZW' => 'Zimbabve', +]; diff --git a/modules/Admin/Language/sr-latn/Dashboard.php b/modules/Admin/Language/sr-latn/Dashboard.php new file mode 100644 index 00000000..8761ee1b --- /dev/null +++ b/modules/Admin/Language/sr-latn/Dashboard.php @@ -0,0 +1,28 @@ + 'Kontrolna tabla za administratora', + 'welcome_message' => 'Dobrodošli u deo za administratore!', + 'podcasts' => [ + 'title' => 'Podkasti', + 'not_found' => 'Nemate objavljen podkast', + 'last_published' => 'Poslednja objava {lastPublicationDate}', + ], + 'episodes' => [ + 'title' => 'Epizode', + 'not_found' => 'Nemate objavljene epizode', + 'last_published' => 'Poslednja objava {lastPublicationDate}', + ], + 'storage' => [ + 'title' => 'Prostor', + 'subtitle' => '{totalUploaded} od {totalStorage}', + ], +]; diff --git a/modules/Admin/Language/sr-latn/Episode.php b/modules/Admin/Language/sr-latn/Episode.php new file mode 100644 index 00000000..9ac56547 --- /dev/null +++ b/modules/Admin/Language/sr-latn/Episode.php @@ -0,0 +1,225 @@ + 'Sezona {seasonNumber}', + 'season_abbr' => 'S{seasonNumber}', + 'number' => 'Epizoda {episodeNumber}', + 'number_abbr' => 'Ep. {episodeNumber}', + 'season_episode' => 'Sezona {seasonNumber} epizoda {episodeNumber}', + 'season_episode_abbr' => 'S{seasonNumber}:E{episodeNumber}', + 'number_of_comments' => '{numberOfComments, plural, + one {# komentar} + other {# komentara} + }', + 'all_podcast_episodes' => 'Sve epizode podkasta', + 'back_to_podcast' => 'Nazad na podkast', + 'edit' => 'Izmeni', + 'preview' => 'Pregled', + 'publish' => 'Objavi', + 'publish_edit' => 'Uredi objavu', + 'publish_date_edit' => 'Uredi datum objave', + 'unpublish' => 'Opozovi objavu', + 'publish_error' => 'Epizoda je već objavljena.', + 'publish_edit_error' => 'Epizoda je već objavljena.', + 'publish_cancel_error' => 'Epizoda je već objavljena.', + 'publish_date_edit_error' => 'Epizoda još uvek nije objavljena, ne možete urediti datum objave.', + 'publish_date_edit_future_error' => 'Datum objavljivanja epizode može se podesiti samo na pređašnji datum. Ukoliko želite da ponovo zakažete objavu epizode u budućnosti, morate prvo opozvati njenu objavu.', + 'publish_date_edit_success' => 'Datum objave epizode je uspešno uređen!', + 'unpublish_error' => 'Epizoda nije objavljena.', + 'delete' => 'Obriši', + 'go_to_page' => 'Idi na stranu', + 'create' => 'Dodaj epizodu', + 'publication_status' => [ + 'published' => 'Objavljeno', + 'with_podcast' => 'Objavljeno', + 'scheduled' => 'Zakazano', + 'not_published' => 'Neobjavljeno', + ], + 'with_podcast_hint' => 'Objaviti u isto vreme kad i podkast', + 'list' => [ + 'search' => [ + 'placeholder' => 'Traži epizodu', + 'clear' => 'Očisti pretragu', + 'submit' => 'Pretraga', + ], + 'number_of_episodes' => '{numberOfEpisodes, plural, + one {# epizoda} + other {# epizode} + }', + 'episode' => 'Epizoda', + 'visibility' => 'Vidljivost', + 'downloads' => 'Preuzimanja', + 'comments' => 'Komentari', + 'actions' => 'Akcije', + ], + 'messages' => [ + 'createSuccess' => 'Epizoda je uspešno kreirana!', + 'editSuccess' => 'Epizoda je uspešno ažurirana!', + 'publishSuccess' => '{publication_status, select, + published {Epizoda je uspešno objavljena!} + scheduled {Epizoda je uspešno zakazana!} + with_podcast {Ova epizoda će biti objavljena u isto vreme kad i podkast.} + other {Ova epizoda nije objavljena.} + }', + 'publishCancelSuccess' => 'Objavljivanje epizode je uspešno otkazano!', + 'unpublishBeforeDeleteTip' => 'Morate opozvati objavljivanje epizode pre nego što je izbrišete.', + 'scheduleDateError' => 'Morate zakazati datum objave!', + 'deletePublishedEpisodeError' => 'Molimo vas opozovite objavu epizode pre nego što je izbrišete.', + 'deleteSuccess' => 'Epizoda uspešno izbrisana!', + 'deleteError' => 'Neuspešno brisanje {type, select, + transcript {transkripta} + chapters {poglavlja} + image {omota} + audio {zvuka} + other {medija} + }.', + 'deleteFileError' => 'Neuspešno brisanje {type, select, + transcript {transkripta} + chapters {poglavlja} + image {omota} + audio {zvuka} + other {medija} + } datoteke {file_key}. Možete je ukloniti ručno sa vašeg diska.', + 'sameSlugError' => 'Odabrano URL ime (slug) epizode već postoji.', + ], + 'form' => [ + 'file_size_error' => + 'Veličina vaše datoteke je prevelika! Maksimalna veličina je {0}. Povećajte `memory_limit`, `upload_max_filesize` i `post_max_size` vrednosti u vašoj datoteci php konfiguracije, potom ponovo pokrenite veb server da bi ste otpremili datoteku.', + 'audio_file' => 'Zvučna datoteka', + 'audio_file_hint' => 'Odaberite .mp3 ili .m4a zvučnu datoteku.', + 'info_section_title' => 'Informacije o epizodi', + 'cover' => 'Omot epizode', + 'cover_hint' => + 'Ukoliko ne postavite omot epizode, koristiće se omot podkasta.', + 'cover_size_hint' => 'Omot mora biti kvadratnog oblika i minimum 1400px širok i visok.', + 'title' => 'Naslov', + 'title_hint' => + 'Treba sadržati jasan i koncizan naziv epizode. Nemojte upisivati broj sezone ili epizode ovde.', + 'permalink' => 'Trajni link', + 'season_number' => 'Sezona', + 'episode_number' => 'Epizoda', + 'type' => [ + 'label' => 'Vrsta', + 'full' => 'Cela', + 'full_hint' => 'Kompletan sadržaj (epizoda)', + 'trailer' => 'Najava', + 'trailer_hint' => 'Kratak, promotivni deo sadržaja koji predstavlja pregled aktuelne emisije', + 'bonus' => 'Bonus', + 'bonus_hint' => 'Dodatni sadržaj za emisiju (na primer, informacije iza scene ili intervjui sa glumcima) ili unakrsni promotivni sadržaj za drugu emisiju', + ], + 'premium_title' => 'Premium', + 'premium' => 'Epizoda mora biti dostupna samo premium pretplatnicima', + 'parental_advisory' => [ + 'label' => 'Roditeljsko savetovanje', + 'hint' => 'Da li epizoda sadrži eksplicitan sadržaj?', + 'undefined' => 'nedefinisano', + 'clean' => 'Čisto', + 'explicit' => 'Eksplicitno', + ], + 'show_notes_section_title' => 'Prikaži beleške', + 'show_notes_section_subtitle' => + 'Do 4000 znakova, budite jasni i sažeti. Beleške pomažu potencijalnim slušaocima da pronađu epizodu.', + 'description' => 'Opis', + 'description_footer' => 'Podnožje opisa', + 'description_footer_hint' => + 'Ovaj tekst se dodaje na kraj opisa svake epizode, ovo je pravo mesto za vaše linkove ka društvenim mrežama naprimer.', + 'additional_files_section_title' => 'Dodatne datoteke', + 'additional_files_section_subtitle' => + 'Ove datoteke mogu biti korišćene od strane drugih platformi radi boljeg iskustva vaše publike. Pogledajte {podcastNamespaceLink} za više informacija.', + 'location_section_title' => 'Lokacija', + 'location_section_subtitle' => 'O kom mestu je ova epizoda?', + 'location_name' => 'Ime ili adresa lokacije', + 'location_name_hint' => 'Ovo može biti prava ili fiktivna lokacija', + 'transcript' => 'Transkript (titlovi)', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', + 'transcript_download' => 'Preuzmi transkript', + 'transcript_file' => 'Transcript file (.srt or .vtt)', + 'transcript_remote_url' => 'Udaljeni Url za transkript', + 'transcript_file_delete' => 'Obriši datoteku transkripta', + 'chapters' => 'Poglavlja', + 'chapters_hint' => 'Datoteka mora biti u JSON Poglavlja formatu.', + 'chapters_download' => 'Preuzmi poglavlja', + 'chapters_file' => 'Datoteka poglavlja', + 'chapters_remote_url' => 'Udaljeni Url za datoteku poglavlja', + 'chapters_file_delete' => 'Obriši datoteku poglavlja', + 'advanced_section_title' => 'Napredni parametri', + 'advanced_section_subtitle' => + 'Ukoliko su vam potrebni RSS tagovi koje Castopod ne obrađuje, postavite ih ovde.', + 'custom_rss' => 'Posebni RSS tagovi epizode', + 'custom_rss_hint' => 'Ovo će biti ubačeno u ❬item❭ tag.', + 'block' => 'Epizoda treba biti sakriivena u javnim katalozima', + 'block_hint' => + 'Prikazan ili sakriven status epizode: ukoliko uključite ovu opciju onemogućavate prikazivanje vaše epizode na paltformama za slušanje podkasta kao što su Apple Podcasts, Google Podcasts i sličnim direktorijima. (Nije zagarantovano)', + 'submit_create' => 'Kreiraj epizodu', + 'submit_edit' => 'Sačuvaj epizodu', + ], + 'publish_form' => [ + 'back_to_episode_dashboard' => 'Nazad na komandnu tablu epizode', + 'post' => 'Najava vaše objave', + 'post_hint' => + "Napišite poruku da najavite objavu vaše epizode. Poruka će biti poslata svim vašim pratiocima u fediversu i istaknuta na stranici vašeg podkasta.", + 'message_placeholder' => 'Napišite poruku…', + 'publication_date' => 'Datum objavljivanja', + 'publication_method' => [ + 'now' => 'Sada', + 'schedule' => 'Raspored', + 'with_podcast' => 'Objavi uz podkast', + ], + 'scheduled_publication_date' => 'Planiran datum objave', + 'scheduled_publication_date_clear' => 'Ukloni datum objave', + 'scheduled_publication_date_hint' => + 'Možete zakazati objavu epizode u budućnosti. Ovo polje mora biti popunjeno u YYYY-MM-DD HH:mm formatu', + 'submit' => 'Objavi', + 'submit_edit' => 'Uredi objavu', + 'cancel_publication' => 'Poništi objavu', + 'message_warning' => 'Niste napisali poruku za najavu objave!', + 'message_warning_hint' => 'Poruka povećava šanse za angažovanjem na društvenim mrežama, rezultirajući u većoj vidljivosti vaše epizode.', + 'message_warning_submit' => 'Objavi svakako', + ], + 'publish_date_edit_form' => [ + 'new_publication_date' => 'Novi datum objavljivanja', + 'new_publication_date_hint' => 'Mora biti podešeno na prošli datum.', + 'submit' => 'Uredi datum objavljivanja', + ], + 'unpublish_form' => [ + 'disclaimer' => + "Opozivanje objavljivanja epizode će obrisati sve komentare i obajve povezane sa eppizodom i ukloniti je i RSS feed-a podkasta.", + 'understand' => 'Razumem, želim da opozovem objavu epizode', + 'submit' => 'Opozovi objavu', + ], + 'delete_form' => [ + 'disclaimer' => + "Brisanje epizode će obrisati sve medijske datoteke, komentare, video i zvučne isečke povezane sa njom.", + 'understand' => 'Razumem, želim da obrišem epizodu', + 'submit' => 'Obriši', + ], + 'embed' => [ + 'title' => 'Embedovan plejer', + 'label' => + 'Odaberite boju teme, kopirajte embedovan plejer i nalepite ga na vaš sajt.', + 'clipboard_iframe' => 'Kopirajte kod embedovanog plejera', + 'clipboard_url' => 'Kopirajte adresu', + 'dark' => 'Tamna', + 'dark-transparent' => 'Tamna providna', + 'light' => 'Svetla', + 'light-transparent' => 'Svetla providna', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'režim nacrta', + 'text' => '{publication_status, select, + published {Ova epizoda još uvek nije objavljena.} + scheduled {Ova epizoda je zakazana za {publication_date}.} + with_podcast {Ova epizoda će biti objavljena kad i podkast.} + other {Ova epizoda još uvek nije objavljena.} + }', + 'preview' => 'Pregled', + ], +]; diff --git a/modules/Admin/Language/sr-latn/EpisodeNavigation.php b/modules/Admin/Language/sr-latn/EpisodeNavigation.php new file mode 100644 index 00000000..48cf7138 --- /dev/null +++ b/modules/Admin/Language/sr-latn/EpisodeNavigation.php @@ -0,0 +1,23 @@ + 'Pogledaj stranicu epizode', + 'dashboard' => 'Kontrolna tabla epizode', + 'episode-view' => 'Početna', + 'episode-edit' => 'Uredi epizodu', + 'episode-persons-manage' => 'Uredi osobe', + 'embed-add' => 'Embedovan plejer', + 'clips' => 'Isečci', + 'video-clips-list' => 'Video isečci', + 'video-clips-create' => 'Novi video isečak', + 'soundbites-list' => 'Zvučni isečci', + 'soundbites-create' => 'Novi zvučni isečak', +]; diff --git a/modules/Admin/Language/sr-latn/Fediverse.php b/modules/Admin/Language/sr-latn/Fediverse.php new file mode 100644 index 00000000..77d2bd18 --- /dev/null +++ b/modules/Admin/Language/sr-latn/Fediverse.php @@ -0,0 +1,32 @@ + [ + 'actorNotFound' => 'Nalog ne može biti pronađen!', + 'blockActorSuccess' => '{actor} je blokiran!', + 'unblockActorSuccess' => '{actor} je blokiran!', + 'blockDomainSuccess' => '{domain} je blokiran!', + 'unblockDomainSuccess' => '{domain} je odblokiran!', + ], + 'blocked_actors' => 'Blokirani nalozi', + 'blocked_domains' => 'Blokirani domeni', + 'block_lists_form' => [ + 'handle' => 'Kvačica naloga', + 'handle_hint' => 'Unesi @username@domain nalog.', + 'domain' => 'Naziv domena', + 'submit' => 'Blokiraj!', + ], + 'list' => [ + 'actor' => 'Nalog', + 'domain' => 'Naziv domena', + 'unblock' => 'Odblokiraj', + ], +]; diff --git a/modules/Admin/Language/sr-latn/Home.php b/modules/Admin/Language/sr-latn/Home.php new file mode 100644 index 00000000..b116bc70 --- /dev/null +++ b/modules/Admin/Language/sr-latn/Home.php @@ -0,0 +1,14 @@ + 'Svi podkasti', + 'no_podcast' => 'Nema pronađenih podkasta', +]; diff --git a/modules/Admin/Language/sr-latn/Install.php b/modules/Admin/Language/sr-latn/Install.php new file mode 100644 index 00000000..65edca65 --- /dev/null +++ b/modules/Admin/Language/sr-latn/Install.php @@ -0,0 +1,61 @@ + 'Ručna konfiguracija', + 'manual_config_subtitle' => + 'Napravite `.env` datoteku sa vašim podešavanjima i osvežite stranicu da bi ste nastavili instalaciju.', + 'form' => [ + 'instance_config' => 'Konfiguracija instance', + 'hostname' => 'Ime domaćina', + 'media_base_url' => 'URL medijske baze', + 'media_base_url_hint' => + 'Ako koristite CDN i/ili eksternu uslugu za analitiku, možete ih postaviti ovde.', + 'admin_gateway' => 'Administratorski izlaz', + 'admin_gateway_hint' => + 'Ruta za pristup kontrolnoj tabli administratora (eg. https://example.com/cp-admin).Podrazumevano je podešena na cp-admin, preporučujemo da je promenite iz sigurnosnih razloga.', + 'auth_gateway' => 'Auth izlaz', + 'auth_gateway_hint' => + 'Ruta za pristup stranicama za potvrdu identiteta (eg. https://example.com/cp-auth).Podrazumevano je podešena na cp-auth, preporučujemo da je promenite iz sigurnosnih razloga.', + 'database_config' => 'Konfiguracija baze podataka', + 'database_config_hint' => + 'Castopod mora da se poveže za vašom MySQL (ili MariaDB) bazom. Ukoliko ne posedujete potrebne informacije, molimo vas kontaktirajte administratora vašeg servera.', + 'db_hostname' => 'Ime hosta baze podataka', + 'db_name' => 'Ime baze podataka', + 'db_username' => 'Korisničko ime baze podataka', + 'db_password' => 'Lozinka baze podataka', + 'db_prefix' => 'Prefiks baze', + 'db_prefix_hint' => + "Prefiks imena tabela Castopod-a, ne diraj ako ne znaš šta znači.", + 'cache_config' => 'Konfiguracija keša', + 'cache_config_hint' => + 'Izaberite željeni obrađivač keša. Ostavite je kao podrazumevanu vrednost ako nemate pojma šta to znači.', + 'cache_handler' => 'Obrađivač keša', + 'cacheHandlerOptions' => [ + 'file' => 'Datoteka', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Sledeće', + 'submit' => 'Završi instalaciju', + 'create_superadmin' => 'Kreiraj svoj nalog super administratora', + 'email' => 'E-pošta', + 'username' => 'Korisničko ime', + 'password' => 'Lozinka', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Vaš nalog superadmina je uspešno kreiran. Prijavite se da biste započeli podkasting!', + 'databaseConnectError' => + 'Castopod nije mogao da se poveže sa vašom bazom podataka. Uredite konfiguraciju baze podataka i pokušajte ponovo.', + 'writeError' => + "Nije moguće kreirati/upisati datoteku `.env`. Morate je kreirati ručno prateći šablon datoteke `.env.example` u Castopod-ovom paketu.", + ], +]; diff --git a/modules/Admin/Language/sr-latn/Navigation.php b/modules/Admin/Language/sr-latn/Navigation.php new file mode 100644 index 00000000..bc4c5518 --- /dev/null +++ b/modules/Admin/Language/sr-latn/Navigation.php @@ -0,0 +1,44 @@ + 'Uključite bočnu traku', + 'go_to_website' => 'Idi na sajt', + 'go_to_admin' => 'Idi na administratora', + 'not-authorized' => 'Not authorized', + 'dashboard' => 'Kontrolna tabla', + 'admin' => 'Početna', + 'podcasts' => 'Podkasti', + 'podcast-list' => 'Svi podkasti', + 'podcast-create' => 'Novi podkast', + 'all-podcast-imports' => 'Uvozi podkasta', + 'podcast-imports-add' => 'Uvezi podkast', + 'persons' => 'Osobe', + 'person-list' => 'Sve osobe', + 'person-create' => 'Nova osoba', + 'fediverse' => 'Fediverzum', + 'fediverse-blocked-actors' => 'Blokirani nalozi', + 'fediverse-blocked-domains' => 'Blokirani domeni', + 'users' => 'Korisnici', + 'user-list' => 'Svi korisnici', + 'user-create' => 'Novi korisnik', + 'pages' => 'Stranice', + 'page-list' => 'Sve stranice', + 'page-create' => 'Nova Stranica', + 'settings' => 'Podešavanja', + 'settings-general' => 'Opšte', + 'settings-theme' => 'Tema', + 'admin-about' => 'Osnovni podaci', + 'account' => [ + 'my-account' => 'Moj nalog', + 'change-password' => 'Promenite lozinku', + 'logout' => 'Odjava', + ], +]; diff --git a/modules/Admin/Language/sr-latn/Notifications.php b/modules/Admin/Language/sr-latn/Notifications.php new file mode 100644 index 00000000..dc79e82a --- /dev/null +++ b/modules/Admin/Language/sr-latn/Notifications.php @@ -0,0 +1,19 @@ + 'Obaveštenja', + 'reply' => '{actor_username} odgovara na tvoju objavu', + 'favourite' => '{actor_username} dodaje tvoju objavu u omiljene', + 'reblog' => '{actor_username} deli tvoju objavu', + 'follow' => '{actor_username} vas sada prati', + 'no_notifications' => 'Nema obaveštenja', + 'mark_all_as_read' => 'Označi svе kao pročitano', +]; diff --git a/modules/Admin/Language/sr-latn/Page.php b/modules/Admin/Language/sr-latn/Page.php new file mode 100644 index 00000000..224fa82e --- /dev/null +++ b/modules/Admin/Language/sr-latn/Page.php @@ -0,0 +1,30 @@ + 'Natrag na početnu', + 'page' => 'Stranica', + 'all_pages' => 'Sve stranice', + 'create' => 'Nova Stranica', + 'go_to_page' => 'Idi na stranicu', + 'edit' => 'Izmeni stranicu', + 'delete' => 'Obriši stranicu', + 'form' => [ + 'title' => 'Naslov', + 'permalink' => 'Trajni link', + 'content' => 'Sadržaj', + 'submit_create' => 'Napravi stranicu', + 'submit_edit' => 'Sačuvaj', + ], + 'messages' => [ + 'createSuccess' => 'Stranica “{pageTitle}” je uspešno napravljena!', + 'editSuccess' => 'Stranica je uspešno ažurirana!', + ], +]; diff --git a/modules/Admin/Language/sr-latn/Pager.php b/modules/Admin/Language/sr-latn/Pager.php new file mode 100644 index 00000000..421dbe64 --- /dev/null +++ b/modules/Admin/Language/sr-latn/Pager.php @@ -0,0 +1,21 @@ + 'Navigacija po stranicama', + 'first' => 'Prva', + 'previous' => 'Prеthodna', + 'next' => 'Sledeća', + 'last' => 'Poslednja', + 'older' => 'Starije', + 'newer' => 'Novije', + 'invalidTemplate' => '{0} nije važeći šablon za stranice.', + 'invalidPaginationGroup' => '{0} nije važeći šablon za grupu paginacija.', +]; diff --git a/modules/Admin/Language/sr-latn/Person.php b/modules/Admin/Language/sr-latn/Person.php new file mode 100644 index 00000000..e2e40df6 --- /dev/null +++ b/modules/Admin/Language/sr-latn/Person.php @@ -0,0 +1,65 @@ + 'Osobe', + 'all_persons' => 'Sve osobe', + 'no_person' => 'Niko nije pronađen!', + 'create' => 'Kreiraj novu osobu', + 'view' => 'Pogledaj osobu', + 'edit' => 'Izmeni osobu', + 'delete' => 'Obriši osobu', + 'messages' => [ + 'createSuccess' => 'Osoba je uspešno kreirana!', + 'editSuccess' => 'Osoba je uspešno izmenjena!', + 'deleteSuccess' => 'Osoba je uklonjena!', + ], + 'form' => [ + 'avatar' => 'Avatar', + 'avatar_size_hint' => + 'Avatar mora biti kvadratnog oblika i minimum 400px širok i visok.', + 'full_name' => 'Puno ime', + 'full_name_hint' => 'Ovo je puno ime ili nadimak osobe.', + 'unique_name' => 'Jedinstveno ime', + 'unique_name_hint' => 'Koristi se za URL', + 'information_url' => 'Informacijski URL', + 'information_url_hint' => + 'Url ka relevantnim informacijama o osobi, kao što su naprimer njihova veb stranica ili profil na društvenim mrežama.', + 'submit_create' => 'Kreiraj novu osobu', + 'submit_edit' => 'Sačuvaj osobu', + ], + 'podcast_form' => [ + 'title' => 'Uredi osobe', + 'add_section_title' => 'Dodaj osobe ovom podkastu', + 'add_section_subtitle' => 'Možete odabrati nekoliko osoba i njihovih uloga.', + 'persons' => 'Osobe', + 'persons_hint' => + 'Možete odabrati jednu ili nekoliko osoba sa istim ulogama. Morate prvo kreirati osobe.', + 'roles' => 'Uloge', + 'roles_hint' => + 'Možete odabrati jednu, nekoliko ili nijednu ulogu za osobu.', + 'submit_add' => 'Dodaj osobu(e)', + 'remove' => 'Ukloni', + ], + 'episode_form' => [ + 'title' => 'Upravljaj osobama', + 'add_section_title' => 'Dodaj osobe ovoj epizodi', + 'add_section_subtitle' => 'Možete odabrati nekoliko osoba i njihovih uloga.', + 'persons' => 'Osobe', + 'persons_hint' => + 'Možete odabrati jednu ili nekoliko osoba sa istim ulogama. Morate prvo kreirati osobe.', + 'roles' => 'Uloge', + 'roles_hint' => + 'Možete odabrati jednu, nekoliko ili nijednu ulogu za osobu.', + 'submit_add' => 'Dodaj osobu(e)', + 'remove' => 'Ukloni', + ], + 'credits' => 'Zasluge', +]; diff --git a/modules/Admin/Language/sr-latn/Platforms.php b/modules/Admin/Language/sr-latn/Platforms.php new file mode 100644 index 00000000..0e895f03 --- /dev/null +++ b/modules/Admin/Language/sr-latn/Platforms.php @@ -0,0 +1,43 @@ + [ + 'podcasting' => 'Podkasting platforme', + 'social' => 'Društvene mreže', + 'funding' => 'Podrška', + ], + 'website' => 'Veb stranica', + 'home_url' => 'Idi na {platformName} stranicu', + 'register' => 'Rеgistrujte sе', + 'submit_url' => 'Dodaj svoj podkast na {platformName}', + 'your_link' => 'Vaš link', + 'your_id' => [ + 'podcasting' => 'Tvoj ID', + 'social' => 'Tvoj ID', + 'funding' => 'Tvoj PNA', + ], + 'your_cta' => 'Tvoj poziv na akciju', + 'visible' => 'Prikaži na naslovnoj strani podkasta?', + 'on_embed' => 'Prikaži na embedovanom plejeru?', + 'remove' => 'Ukloni {platformName}', + 'submit' => 'Sačuvaj', + 'messages' => [ + 'updateSuccess' => 'Veze sa platformama su uspešno ažurirane!', + 'removeLinkSuccess' => 'Veza ka platformi je izbrisana.', + 'removeLinkError' => + 'Vezu sa platformom nije moguće ukloniti. Probajte ponovo.', + ], + 'description' => [ + 'podcasting' => 'ID podkasta na ovoj platformi', + 'social' => 'ID naloga podkasta na ovoj platformi', + 'funding' => 'Poruka poziva na akciju', + ], +]; diff --git a/modules/Admin/Language/sr-latn/Podcast.php b/modules/Admin/Language/sr-latn/Podcast.php new file mode 100644 index 00000000..7926e093 --- /dev/null +++ b/modules/Admin/Language/sr-latn/Podcast.php @@ -0,0 +1,330 @@ + 'Svi podkasti', + 'no_podcast' => 'Nema pronađenih podkasta!', + 'create' => 'Napravi podkast', + 'import' => 'Uvezi podkast', + 'all_imports' => 'Uvozi podkasta', + 'new_episode' => 'Nova epizoda', + 'view' => 'Pogledaj epizodu', + 'edit' => 'Uredi podkast', + 'publish' => 'Objavi podkast', + 'publish_edit' => 'Uredi objavu', + 'delete' => 'Obriši podkast', + 'see_episodes' => 'Pogledaj epizode', + 'see_contributors' => 'Pogledaj saradnike', + 'monetization_other' => 'Druga monetizacija', + 'go_to_page' => 'Idi na stranicu', + 'latest_episodes' => 'Najnovije epizode', + 'see_all_episodes' => 'Prikaži sve epizode', + 'draft' => 'Nacrt', + 'messages' => [ + 'createSuccess' => 'Podkast uspešno kreiran!', + 'editSuccess' => 'Podkast je uspešno ažuriran!', + 'importSuccess' => 'Podkast je uspešno uvezen!', + 'deleteSuccess' => 'Podkast @{podcast_handle} je uspešno obrisan!', + 'deletePodcastMediaError' => 'Neuspešno brisanje podkast {type, select, + cover {omota} + banner {banera} + other {medija} + }.', + 'deleteEpisodeMediaError' => 'Neuspešno brisanje {episode_slug} {type, select, + transcript {transkripta} + chapters {poglavlja} + image {omota} + audio {zvuka} + other {medija} + }.', + 'deletePodcastMediaFolderError' => 'Neuspešno brisanje podkast medija direktorijuma {folder_path}. Možete ga ručno ukloniti sa diska.', + 'podcastFeedUpdateSuccess' => 'Uspešno ažuriranje: {number_of_new_episodes, plural, + one {# epizoda je} + other {# epizode su} + } deo podkasta!', + 'podcastFeedUpToDate' => 'Podkast je već ažuriran.', + 'publishError' => 'Ovaj podkast je ili već objavljen ili zakazan za objavu.', + 'publishEditError' => 'Ovaj podkast nije zakazan za objavu.', + 'publishCancelSuccess' => 'Objavljivanje podkasta je uspešno otkazano!', + 'scheduleDateError' => 'Morate zakazati datum objave!', + ], + 'form' => [ + 'identity_section_title' => 'Identitet podkasta', + 'identity_section_subtitle' => 'Ova polja vam pomažu da budete prepoznati.', + 'fediverse_section_title' => 'Fedivers identitet', + + 'cover' => 'Omot podkasta', + 'cover_size_hint' => 'Omot mora biti kvadratnog oblika i minimum 1400px širok i visok.', + 'banner' => 'Baner podkasta', + 'banner_size_hint' => 'Baner mora imati odnos 3:1 i biti najmanje 1500px širok.', + 'banner_delete' => 'Obriši baner podkasta', + 'title' => 'Naslov', + 'handle' => 'Ručka', + 'handle_hint' => + 'Koristi se radi identifikacije podkasta. Velika slova, mala slova, brojevi i donja crta su prihvatljivi.', + 'type' => [ + 'label' => 'Vrsta', + 'episodic' => 'Epizodno', + 'episodic_hint' => 'Ukoliko su epizode namenjene za konzumiranje bez nekog specifičnog reda. Najnovija epizoda će biti predstavljena prva u redosledu.', + 'serial' => 'Serijski', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + ], + 'description' => 'Opis', + 'classification_section_title' => 'Klasifikacija', + 'classification_section_subtitle' => + 'Ova polja će uticati na vašu publiku i konkurenciju.', + 'language' => 'Jezik', + 'category' => 'Kategorija', + 'category_placeholder' => 'Izaberite kategoriju…', + 'other_categories' => 'Ostale kategorije', + 'parental_advisory' => [ + 'label' => 'Upozorenje za roditelje', + 'hint' => 'Da li sadrži eksplicitan sadržaj?', + 'undefined' => 'nedefinisano', + 'clean' => 'Čisto', + 'explicit' => 'Eksplicitno', + ], + 'author_section_title' => 'Autor', + 'author_section_subtitle' => 'Ko upravlja podkastom?', + 'owner_name' => 'Ime vlasnika', + 'owner_name_hint' => + 'Za administrativnu upotrebu. Vidljivo u javnom RSS feed-u.', + 'owner_email' => 'Elektronska pošta vlasnika', + 'owner_email_hint' => + 'Koristiće se na većini platforma kako bi se utvrdilo vlasništvo nad podkastom. Vidljivo javno u RSS feed-u.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', + 'publisher' => 'Izdavač', + 'publisher_hint' => + 'Grupa odgovorna za stvaranje ove emisije. Često se misli na vlasničku kompaniju ili mrežu kojoj pripada podkast. Ovo polje se nekada naziva i \'Autor".', + 'copyright' => 'Autorsko pravo', + 'location_section_title' => 'Lokacija', + 'location_section_subtitle' => 'O kom mestu je ova epizoda?', + 'location_name' => 'Ime ili adresa lokacije', + 'location_name_hint' => 'Ovo može biti stvarno ili izmišljeno mesto', + 'monetization_section_title' => 'Monetizacija', + 'monetization_section_subtitle' => + 'Zaradi novac zahvaljujući svojoj publici.', + 'premium' => 'Premijum', + 'premium_by_default' => 'Epizode ​​se podrazumevano moraju podesiti kao premijum', + 'premium_by_default_hint' => 'Epizode ​​podkasta će podrazumevano biti označene kao premijum. I dalje možete izabrati da neke epizode, trejlere ili bonuse postavite kao javne.', + 'op3' => 'Otvoreni Podkast Prefiks Projekat (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Vrednujte svoje analitičke podatke pomoću OP3, otvorenog koda i pouzdane analitičke usluge treće strane. Delite, potvrdite i uporedite svoje analitičke podatke sa otvorenim podkast ekosistemom.', + 'op3_enable' => 'Omogućite OP3 analitičku uslugu', + 'op3_enable_hint' => 'Iz bezbednosnih razloga, analitika premijum epizoda se neće deliti sa OP3.', + 'payment_pointer' => 'Pokazivač plaćanja za Veb monetizaciju', + 'payment_pointer_hint' => + 'Ovde ćete primati novac zahvaljujući Veb monetizaciji', + 'advanced_section_title' => 'Napredni parametri', + 'advanced_section_subtitle' => + 'Ukoliko su vam potrebni RSS tagovi koje Castopod ne obrađuje, postavite ih ovde.', + 'custom_rss' => 'Posebni RSS tagovi epizode', + 'custom_rss_hint' => 'Ovo će biti ubačeno u ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', + 'new_feed_url' => 'Novi URL fid', + 'new_feed_url_hint' => 'Koristite ovo polje kada prelazite na drugi domen ili platformu za hostovanje podkasta. Podrazumevano, vrednost je podešena na trenutni RSS URL ako je podkast uvezen.', + 'old_feed_url' => 'Stari URL fid', + 'partnership' => 'Partnerstvo', + 'partner_id' => 'ID', + 'partner_link_url' => 'URL adresa veze', + 'partner_image_url' => 'URL adresa slike', + 'partner_id_hint' => 'Vaš partnerski ID', + 'partner_link_url_hint' => 'Generička adresa veze partnera', + 'partner_image_url_hint' => 'Generička adresa slike partnera', + 'block' => 'Podkast treba sakriti iz javnih kataloga', + 'block_hint' => + 'Prikazan ili sakriven status podkasta: ukoliko uključite ovu opciju onemogućavate prikazivanje vašeg podkasta na paltformama za slušanje podkasta kao što su Apple Podcasts, Google Podcasts i sličnim direktorijima. (Nije zagarantovano)', + 'complete' => 'Podkast više neće imati novih epizoda', + 'lock' => 'Sprečite kopiranje podkasta', + 'lock_hint' => + 'Cilj ovoga je da komunicira sa drugim podkast platformama i ne dozvoli im da povlače vaš sadržaj. Ukoliko odaberete Da, to znači da će svaki njihov pokušaj da izlistaju vaš sadržaj na svojoj platformi biti odbijen.', + 'submit_create' => 'Napravi podkast', + 'submit_edit' => 'Sačuvaj podkast', + ], + 'category_options' => [ + 'uncategorized' => 'nekategorizovano', + 'arts' => 'Umetnost', + 'business' => 'Posao', + 'comedy' => 'Komedija', + 'education' => 'Obrazovanje', + 'fiction' => 'Fikcija', + 'government' => 'Vlada', + 'health_and_fitness' => 'Zdravlje i Fitnes', + 'history' => 'Istorija', + 'kids_and_family' => 'Deca i Porodica', + 'leisure' => 'Razonoda', + 'music' => 'Muzika', + 'news' => 'Vesti', + 'religion_and_spirituality' => 'Religija i spiritualnost', + 'science' => 'Nauka', + 'society_and_culture' => 'Društvo i Kultura', + 'sports' => 'Sport', + 'technology' => 'Tehnologija', + 'true_crime' => 'Istinski zločini', + 'tv_and_film' => 'Televizija i Film', + 'books' => 'Knjige', + 'design' => 'Dizajn', + 'fashion_and_beauty' => 'Moda i Lepota', + 'food' => 'Hrana', + 'performing_arts' => 'Izvođačka umetnost', + 'visual_arts' => 'Likovna umetnost', + 'careers' => 'Karijera', + 'entrepreneurship' => 'Prednuzetništvo', + 'investing' => 'Investiranje', + 'management' => 'Upravljanje', + 'marketing' => 'Marketing', + 'non_profit' => 'Neprofitna udruženja', + 'comedy_interviews' => 'Komični intervjui', + 'improv' => 'Improvizacija', + 'stand_up' => 'Stendap komedija', + 'courses' => 'Kursevi', + 'how_to' => 'Uradi sam', + 'language_learning' => 'Učenje jezika', + 'self_improvement' => 'Samopoboljšanje', + 'comedy_fiction' => 'Komična fantastika', + 'drama' => 'Drama', + 'science_fiction' => 'Naučna Fantastika', + 'alternative_health' => 'Alternativno zdravlje', + 'fitness' => 'Fitnes', + 'medicine' => 'Medicina', + 'mental_health' => 'Mentalno zdravlje', + 'nutrition' => 'Nutricionizam', + 'sexuality' => 'Seksualnost', + 'education_for_kids' => 'Obrazovanje dece', + 'parenting' => 'Roditeljstvo', + 'pets_and_animals' => 'Ljubimci i životinje', + 'stories_for_kids' => 'Priče za decu', + 'animation_and_manga' => 'Animacija i Manga', + 'automotive' => 'Automobilizam', + 'aviation' => 'Avijacija', + 'crafts' => 'Zanati', + 'games' => 'Igre', + 'hobbies' => 'Hobiji', + 'home_and_garden' => 'Dom i bašta', + 'video_games' => 'Video igre', + 'music_commentary' => 'Komentari muzike', + 'music_history' => 'Istorija muzike', + 'music_interviews' => 'Muzički intervjui', + 'business_news' => 'Vesti iz preduzetništva', + 'daily_news' => 'Dnevne vesti', + 'entertainment_news' => 'Vesti iz zabave', + 'news_commentary' => 'Komentari vesti', + 'politics' => 'Politika', + 'sports_news' => 'Sportske vesti', + 'tech_news' => 'Tehnološke vesti', + 'buddhism' => 'Budizam', + 'christianity' => 'Hrišćanstvo', + 'hinduism' => 'Hinduizam', + 'islam' => 'Islam', + 'judaism' => 'Judeizam', + 'religion' => 'Religija', + 'spirituality' => 'Duhovnost', + 'astronomy' => 'Astronomija', + 'chemistry' => 'Hemija', + 'earth_sciences' => 'Studije zemlje', + 'life_sciences' => 'Studije života', + 'mathematics' => 'Matematika', + 'natural_sciences' => 'Prirodne nauke', + 'nature' => 'Priroda', + 'physics' => 'Fizika', + 'social_sciences' => 'Društvene nauke', + 'documentary' => 'Dokumentarni', + 'personal_journals' => 'Lični dnevnici', + 'philosophy' => 'Filozofija', + 'places_and_travel' => 'Mesta i Putovanje', + 'relationships' => 'Veze', + 'baseball' => 'Bejzbol', + 'basketball' => 'Košarka', + 'cricket' => 'Kriket', + 'fantasy_sports' => 'Fantazi sport', + 'football' => 'Američki fudbal', + 'golf' => 'Golf', + 'hockey' => 'Hokej', + 'rugby' => 'Ragbi', + 'running' => 'Trčanje', + 'soccer' => 'Fudbal', + 'swimming' => 'Plivanje', + 'tennis' => 'Tenis', + 'volleyball' => 'Odbojka', + 'wilderness' => 'Divljina', + 'wrestling' => 'Rvanje', + 'after_shows' => 'Posle emisija', + 'film_history' => 'Filmska istorija', + 'film_interviews' => 'Filmski intervjui', + 'film_reviews' => 'Filmske recenzije', + 'tv_reviews' => 'Televizijske recenzije', + ], + 'publish_form' => [ + 'back_to_podcast_dashboard' => 'Nazad na komandnu tablu podkasta', + 'post' => 'Najava vaše objave', + 'post_hint' => + "Napišite poruku kako bi ste najavili objavljivanje vašeg podkasta. Ova poruka će biti istaknuta na početnoj stranici vašeg podkasta.", + 'message_placeholder' => 'Napišite poruku…', + 'submit' => 'Objavi', + 'publication_date' => 'Datum objavljivanja', + 'publication_method' => [ + 'now' => 'Sada', + 'schedule' => 'Raspored', + ], + 'scheduled_publication_date' => 'Planiran datum objave', + 'scheduled_publication_date_hint' => + 'Možete zakazati objavu podkasta u budućnosti. Ovo polje mora biti popunjeno u YYYY-MM-DD HH:mm formatu', + 'submit_edit' => 'Uredi objavu', + 'cancel_publication' => 'Poništi objavu', + 'message_warning' => 'Niste napisali poruku za najavu objave!', + 'message_warning_hint' => 'Poruka povećava šanse za angažovanjem na društvenim mrežama, rezultirajući u većoj vidljivosti vašeg podkasta.', + 'message_warning_submit' => 'Objavi svakako', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'režim nacrta', + 'not_published' => 'Ovaj podkast nije još uvek objavljen.', + 'scheduled' => 'Ovaj podkast je zakazan za objavu {publication_date}.', + ], + 'delete_form' => [ + 'disclaimer' => + "Brisanjem podkasta obrisaće se i sve epizode, medijske datoteke, objave i analitika povezana sa njim. Ova radnja je nepovratna, nakon toga nećete više moći da ih preuzmete ili povratite.", + 'understand' => 'Razumem, želim da trajno obrišem podkast', + 'submit' => 'Obriši', + ], + 'by' => 'Od {publisher}', + 'season' => 'Sezona {seasonNumber}', + 'list_of_episodes_year' => '{year} epizoda ({episodeCount})', + 'list_of_episodes_season' => + 'Sezona {seasonNumber} epizoda ({episodeCount})', + 'no_episode' => 'Nijedna epizode nije pronađena!', + 'follow' => 'Prati', + 'followers' => '{numberOfFollowers, plural, + one {# pratilac} + other {# pratilaca} + }', + 'posts' => '{numberOfPosts, plural, + one {# objava} + other {# objave} + }', + 'activity' => 'Aktivnost', + 'episodes' => 'Epizode', + 'sponsor' => 'Sponzor', + 'funding_links' => 'Linkovi za finansiranje {podcastTitle}', + 'find_on' => 'Pronađi {podcastTitle} na', + 'listen_on' => 'Slušaj na', +]; diff --git a/modules/Admin/Language/sr-latn/PodcastNavigation.php b/modules/Admin/Language/sr-latn/PodcastNavigation.php new file mode 100644 index 00000000..5958a625 --- /dev/null +++ b/modules/Admin/Language/sr-latn/PodcastNavigation.php @@ -0,0 +1,42 @@ + 'Idite na stranu podkasta', + 'rss_feed' => 'RSS izvor', + 'dashboard' => 'Kontrolna strana podkasta', + 'podcast-view' => 'Početna stranica', + 'podcast-edit' => 'Uredi podkast', + 'podcast-persons-manage' => 'Upravljaj osobama', + 'podcast-imports' => 'Uvozi podkasta', + 'podcast-imports-sync' => 'Sinhronizuj snabdevače', + 'episodes' => 'Epizode', + 'episode-list' => 'Sve Epizode', + 'episode-create' => 'Nova epizoda', + 'analytics' => 'Analitika', + 'podcast-analytics' => 'Pregled publike', + 'podcast-analytics-webpages' => 'Poseta Veb stranici', + 'podcast-analytics-locations' => 'Lokacije', + 'podcast-analytics-unique-listeners' => 'Jedinstveni slušaoci', + 'podcast-analytics-players' => 'Plejeri', + 'podcast-analytics-listening-time' => 'Ukupno vreme slušanja', + 'podcast-analytics-time-periods' => 'Vremenski periodi', + 'monetization' => 'Monetizacija', + 'subscription-list' => 'Sve pretplate', + 'subscription-create' => 'Dodaj pretplatu', + 'contributors' => 'Saradnici', + 'contributor-list' => 'Svi saradnici', + 'contributor-add' => 'Dodaj saradnika', + 'broadcast' => 'Emitovanje', + 'platforms-podcasting' => 'Podkasting aplikacije', + 'platforms-social' => 'Društvene mreže', + 'platforms-funding' => 'Podrška', + 'podcast-monetization-other' => 'Drugo', +]; diff --git a/modules/Admin/Language/sr-latn/Settings.php b/modules/Admin/Language/sr-latn/Settings.php new file mode 100644 index 00000000..82554b3c --- /dev/null +++ b/modules/Admin/Language/sr-latn/Settings.php @@ -0,0 +1,58 @@ + 'Opšta podešavanja', + 'instance' => [ + 'title' => 'Instance', + 'site_icon' => 'Ikonica sajta', + 'site_icon_delete' => 'Obriši ikonicu sajta', + 'site_icon_hint' => 'Ikone sajtova su ono što vidite na karticama pregledača, traci sa obeleživačima i kada dodate veb lokaciju kao prečicu na mobilnim uređajima.', + 'site_icon_helper' => 'Ikona mora biti kvadratnog oblika i minimum 512px široka i visoka.', + 'site_name' => 'Naziv veb strane', + 'site_description' => 'Opis veb strane', + 'submit' => 'Sačuvaj', + 'editSuccess' => 'Instanca je uspešno ažurirana!', + 'deleteIconSuccess' => 'Ikona veb strane je uspešno uklonjena!', + ], + 'images' => [ + 'title' => 'Slike', + 'subtitle' => 'Ovde možete regenerisati sve slike na osnovu originala koji su otpremljeni. Koristi se ako ustanovite da neke slike nedostaju. Ovaj zadatak može potrajati.', + 'regenerate' => 'Regeneriši slike', + 'regenerationSuccess' => 'Sve slike su uspešno regenerisane!', + ], + 'housekeeping' => [ + 'title' => 'Održavanje', + 'subtitle' => 'Obavlja razne poslove u održavanju. Koristite ovu funkciju ako ikada naiđete na probleme sa medijskim datotekama ili integritetom podataka. Ovi zadaci mogu potrajati.', + 'reset_counts' => 'Resetujte brojače', + 'reset_counts_helper' => 'Ova opcija će ponovo izračunati i resetovati sve podatke (broj pratilaca, objava, komentara,…).', + 'rewrite_media' => 'Ponovo upiši medijske metapodatke', + 'rewrite_media_helper' => 'Ova opcija će izbrisati sve suvišne medijske datoteke i ponovo ih kreirati (slike, audio datoteke, transkripte, poglavlja,…)', + 'rename_episodes_files' => 'Preimenuj audio datoteku epizode', + 'rename_episodes_files_hint' => 'Ova opcija će preimenovati sve audio datoteke epizoda u nasumični niz znakova. Koristite ovo ako je neka od vaših privatnih epizoda procurila jer će je to efektivno sakriti.', + 'clear_cache' => 'Obriši sav keš', + 'clear_cache_helper' => 'Ova opcija će isprazniti redis keš ili datoteke za pisanje/keširanje.', + 'run' => 'Pokreni održavanje', + 'runSuccess' => 'Održavanje je uspešno obavljeno!', + ], + 'theme' => [ + 'title' => 'Tema', + 'accent_section_title' => 'Naglašena boja', + 'accent_section_subtitle' => 'Izaberite boju da biste odredili izgled svih javnih stranica.', + 'pine' => 'Bor zelena', + 'crimson' => 'Tamnocrvena', + 'amber' => 'Ćilibar', + 'lake' => 'Jezero plava', + 'jacaranda' => 'Jakaranda ljubičasta', + 'onyx' => 'Oniks crna', + 'submit' => 'Sačuvaj', + 'setInstanceThemeSuccess' => 'Tema je uspešno ažurirana!', + ], +]; diff --git a/modules/Admin/Language/sr-latn/Soundbite.php b/modules/Admin/Language/sr-latn/Soundbite.php new file mode 100644 index 00000000..306681f9 --- /dev/null +++ b/modules/Admin/Language/sr-latn/Soundbite.php @@ -0,0 +1,31 @@ + [ + 'title' => 'Zvučni isečci', + 'soundbite' => 'Zvučni isečak', + ], + 'messages' => [ + 'createSuccess' => 'Zvučni isečak je uspešno kreiran!', + 'deleteSuccess' => 'Zvučni isečak je uspešno uklonjen!', + ], + 'form' => [ + 'title' => 'Novi zvučni isečak', + 'soundbite_title' => 'Naziv zvučnog isečka', + 'start_time' => 'Počni na', + 'duration' => 'Trajanje', + 'submit' => 'Napravi zvučni isečak', + ], + 'play' => 'Reprodukuj zvučni isečak', + 'stop' => 'Zaustavi zvučni isečak', + 'create' => 'Novi zvučni isečak', + 'delete' => 'Obriši zvučni isečak', +]; diff --git a/modules/Admin/Language/sr-latn/Validation.php b/modules/Admin/Language/sr-latn/Validation.php new file mode 100644 index 00000000..d83783d4 --- /dev/null +++ b/modules/Admin/Language/sr-latn/Validation.php @@ -0,0 +1,17 @@ + + '{field} ili nije slika ili nije dovoljne dužine/visine.', + 'is_image_ratio' => + '{field} ili nije slike ili nije u pravom odnosu veličina.', + 'is_json' => '{field} contains invalid JSON.', +]; diff --git a/modules/Admin/Language/sr-latn/VideoClip.php b/modules/Admin/Language/sr-latn/VideoClip.php new file mode 100644 index 00000000..aa0d795b --- /dev/null +++ b/modules/Admin/Language/sr-latn/VideoClip.php @@ -0,0 +1,72 @@ + [ + 'title' => 'Video isečci', + 'status' => [ + 'label' => 'Status', + 'queued' => 'čekanje', + 'queued_hint' => 'Video isečak čeka na obradu.', + 'pending' => 'na čekanju', + 'pending_hint' => 'Video isečak će uskoro biti napravljen.', + 'running' => 'u toku', + 'running_hint' => 'Video isečak se pravi.', + 'failed' => 'nije uspеlo', + 'failed_hint' => 'Pravljenje video isečka nije uspelo: greška u skripti.', + 'passed' => 'prošlo', + 'passed_hint' => 'Video isečak je uspešno napravljen!', + ], + 'clip' => 'Isečak', + 'duration' => 'Trajanje posla', + ], + 'title' => 'Video Isečak: {videoClipLabel}', + 'download_clip' => 'Preuzmi isečak', + 'create' => 'Novi video isečak', + 'go_to_page' => 'Idi na stranicu isečka', + 'retry' => 'Ponovo pokušaj da napraviš isečak', + 'delete' => 'Obriši isečak', + 'logs' => 'Katalog poslova', + 'messages' => [ + 'alreadyExistingError' => 'Video isečak koji pokušavate da napravite već postoji!', + 'addToQueueSuccess' => 'Video isečak je dodat u katalog poslova, čeka da bude napravljen!', + 'deleteSuccess' => 'Video isečak je uspešno uklonjen!', + ], + 'format' => [ + 'landscape' => 'Položeno', + 'portrait' => 'Uspravno', + 'squared' => 'Kvadratno', + ], + 'form' => [ + 'title' => 'Novi video isečak', + 'params_section_title' => 'Parametri video isečka', + 'clip_title' => 'Naziv isečka', + 'format' => [ + 'label' => 'Odaberite format', + 'landscape_hint' => 'U 16:9 formatu, položeni video klipovi su odlični za platforme kao što su PeerTube, YouTube i Vimeo.', + 'portrait_hint' => 'U 9:16 formatu, uspravni video isečci su odlični za TikTok, YouTube shorts i Instagram stories.', + 'squared_hint' => 'U 1:1 formatu, kvadratni video isečci su odlični za Mastodon, Facebook, Twitter i LinkedIn.', + ], + 'theme' => 'Odaberite temu', + 'start_time' => 'Počni na', + 'duration' => 'Trajanje', + 'trim_start' => 'Početak isečka', + 'trim_end' => 'Kraj isečka', + 'submit' => 'Napravi video isečak', + ], + 'requirements' => [ + 'title' => 'Nedostaje polje', + 'missing' => 'Niste popunili sva polja. Potrudite se da ubacite sve što je potrebno kako bi ste napravili video za ovu epizodu!', + 'ffmpeg' => 'FFmpeg', + 'gd' => 'Graphics Draw (GD)', + 'freetype' => 'Freetype library for GD', + 'transcript' => 'Datoteka transkripta (.srt)', + ], +]; diff --git a/modules/Admin/Language/sv/AboutCastopod.php b/modules/Admin/Language/sv/AboutCastopod.php new file mode 100644 index 00000000..f9707cbe --- /dev/null +++ b/modules/Admin/Language/sv/AboutCastopod.php @@ -0,0 +1,22 @@ + 'Om Castopod', + 'host_name' => 'Värdnamn', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operativsystem', + 'languages' => 'Språk', + 'update_database' => 'Uppdatera databas', + 'messages' => [ + 'databaseUpdateSuccess' => 'Databasen är uppdaterad!', + ], +]; diff --git a/modules/Admin/Language/sv/Breadcrumb.php b/modules/Admin/Language/sv/Breadcrumb.php index f3269bfa..7acad68e 100644 --- a/modules/Admin/Language/sv/Breadcrumb.php +++ b/modules/Admin/Language/sv/Breadcrumb.php @@ -11,42 +11,47 @@ declare(strict_types=1); return [ 'label' => 'breadcrumb', config('Admin') - ->gateway => 'Home', + ->gateway => 'Hem', 'podcasts' => 'podcasts', - 'episodes' => 'episodes', - 'subscriptions' => 'subscriptions', - 'contributors' => 'contributors', - 'pages' => 'pages', - 'settings' => 'settings', - 'theme' => 'theme', - 'add' => 'add', - 'new' => 'new', - 'edit' => 'edit', - 'persons' => 'persons', - 'publish' => 'publish', - 'publish-edit' => 'edit publication', - 'publish-date-edit' => 'edit publication date', - 'unpublish' => 'unpublish', - 'delete' => 'delete', + 'episodes' => 'avsnitt', + 'subscriptions' => 'prenumerationer', + 'contributors' => 'bidragsgivare', + 'pages' => 'sidor', + 'settings' => 'inställningar', + 'theme' => 'tema', + 'about' => 'om', + 'add' => 'lägg till', + 'new' => 'ny', + 'edit' => 'redigera', + 'persons' => 'personer', + 'publish' => 'publicera', + 'publish-edit' => 'redigera publikation', + 'publish-date-edit' => 'redigera publiceringsdatum', + 'unpublish' => 'avpublicera', + 'delete' => 'radera', + 'remove' => 'ta bort', 'fediverse' => 'fediverse', - 'block-lists' => 'block lists', - 'users' => 'users', - 'my-account' => 'my account', - 'change-password' => 'change password', - 'import' => 'feed import', - 'platforms' => 'platforms', - 'social' => 'social networks', - 'funding' => 'funding', - 'analytics' => 'analytics', - 'locations' => 'locations', - 'webpages' => 'web pages', - 'unique-listeners' => 'unique listeners', - 'players' => 'players', - 'listening-time' => 'listening time', - 'time-periods' => 'time periods', - 'soundbites' => 'soundbites', - 'video-clips' => 'video clips', - 'embed' => 'embeddable player', - 'notifications' => 'notifications', - 'suspend' => 'suspend', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', + 'users' => 'användare', + 'my-account' => 'mitt konto', + 'change-password' => 'ändra lösenord', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', + 'platforms' => 'plattformar', + 'social' => 'sociala nätverk', + 'funding' => 'finansiering', + 'monetization-other' => 'other monetization', + 'analytics' => 'analys', + 'locations' => 'platser', + 'webpages' => 'webbplatser', + 'unique-listeners' => 'unika lyssnare', + 'players' => 'spelare', + 'listening-time' => 'lyssningstid', + 'time-periods' => 'tidsperiod', + 'soundbites' => 'ljudklipp', + 'video-clips' => 'videoklipp', + 'embed' => 'inbäddad spelare', + 'notifications' => 'aviseringar', + 'suspend' => 'suspendera', ]; diff --git a/modules/Admin/Language/sv/Charts.php b/modules/Admin/Language/sv/Charts.php index 4b33530e..868b40fd 100644 --- a/modules/Admin/Language/sv/Charts.php +++ b/modules/Admin/Language/sv/Charts.php @@ -9,32 +9,33 @@ declare(strict_types=1); */ return [ - 'by_service_weekly' => 'Episode downloads by service (for the past week)', - 'by_player_weekly' => 'Episode downloads by player (for the past week)', - 'by_player_yearly' => 'Episode downloads by player (for the past year)', - 'by_device_weekly' => 'Episode downloads by device (for the past week)', - 'by_os_weekly' => 'Episode downloads by O.S. (for the past week)', - 'podcast_by_region' => 'Episode downloads by region (for the past week)', - 'unique_daily_listeners' => 'Daily unique listeners', - 'unique_monthly_listeners' => 'Monthly unique listeners', - 'by_browser' => 'Web pages usage by browser (for the past week)', - 'podcast_by_day' => 'Episode daily downloads', - 'podcast_by_month' => 'Episode monthly downloads', - 'episode_by_day' => 'Episode daily downloads (first 60 days)', - 'episode_by_month' => 'Episode monthly downloads', + 'by_service_weekly' => 'Avsnitt nedladdningar via tjänst (under den senaste veckan)', + 'by_player_weekly' => 'Nedladdningar av avsnitt via spelare (under den senaste veckan)', + 'by_player_yearly' => 'Nedladdningar av avsnitt via spelare (under det gångna året)', + 'by_device_weekly' => 'Avsnitt nedladdningar per enhet (föregående veckan)', + 'by_os_weekly' => 'Avsnitt nedladdningar av O.S. (för den föregående veckan)', + 'podcast_by_region' => 'Nedladdningar av avsnitt efter region (under den föregående veckan)', + 'unique_daily_listeners' => 'Dagliga unika lyssnare', + 'unique_monthly_listeners' => 'Unika lyssnare varje månad', + 'by_browser' => 'Webbsidor användning av webbläsare (under den föregående veckan)', + 'podcast_by_day' => 'Avsnitt dagliga nedladdningar', + 'podcast_by_month' => 'Månatliga nedladdningar av avsnitt', + 'episode_by_day' => 'Avsnitt dagliga nedladdningar (första 60 dagar)', + 'episode_by_month' => 'Månatliga nedladdningar av avsnitt', 'episodes_by_day' => - '5 latest episodes downloads (during their first 60 days)', - 'by_country_weekly' => 'Episode downloads by country (for the past week)', - 'by_country_yearly' => 'Episode downloads by country (for the past year)', - 'by_domain_weekly' => 'Web pages visits by source (for the past week)', - 'by_domain_yearly' => 'Web pages visits by source (for the past year)', - 'by_entry_page' => 'Web pages visits by landing page (for the past week)', + '5 senaste avsnitt nedladdningar (under sina första 60 dagar)', + 'by_country_weekly' => 'Nedladdningar av avsnitt per land (under den föregående veckan)', + 'by_country_yearly' => 'Nedladdningar av avsnitt per land (för föregående året)', + 'by_domain_weekly' => 'Webbsidor besök per källa (under föregående veckan)', + 'by_domain_yearly' => 'Webbsidor besök per källa (för det gångna året)', + 'by_entry_page' => 'Webbsidor besök via startsida (under den föregående veckan)', 'podcast_bots' => 'Bots (crawlers)', - 'daily_listening_time' => 'Daily cumulative listening time', - 'monthly_listening_time' => 'Monthly cumulative listening time', - 'by_weekday' => 'By week day (for the past 60 days)', - 'by_hour' => 'By time of day (for the past 60 days)', - 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', - 'total_storage_by_month' => 'Monthly storage (in MB)', - 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'daily_listening_time' => 'Daglig kumulativ lyssningstid', + 'monthly_listening_time' => 'Månatlig kumulativ lyssningstid', + 'by_weekday' => 'Efter veckodag (för de senaste 60 dagarna)', + 'by_hour' => 'Via tiden på dagen (de senaste 60 dagarna)', + 'podcast_by_bandwidth' => 'Dagligen använd bandbredd (i MB)', + 'total_storage_by_month' => 'Månadslagring (i MB)', + 'total_bandwidth_by_month' => 'Månatlig använd bandbredd (i MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', ]; diff --git a/modules/Admin/Language/sv/Common.php b/modules/Admin/Language/sv/Common.php index 596c8bcd..465f8c4c 100644 --- a/modules/Admin/Language/sv/Common.php +++ b/modules/Admin/Language/sv/Common.php @@ -9,43 +9,44 @@ declare(strict_types=1); */ return [ - 'yes' => 'Yes', - 'no' => 'No', - 'cancel' => 'Cancel', - 'optional' => 'Optional', - 'more' => 'More', - 'no_data' => 'No data found!', - 'close' => 'Close', - 'edit' => 'Edit', - 'copy' => 'Copy', - 'copied' => 'Copied!', - 'home' => 'Home', - 'explicit' => 'Explicit', - 'powered_by' => 'Powered by {castopod}', - 'actions' => 'Actions', - 'pageInfo' => 'Page {currentPage} out of {pageCount}', - 'go_back' => 'Go back', + 'yes' => 'Ja', + 'no' => 'Nej', + 'cancel' => 'Avbryt', + 'optional' => 'Valfritt', + 'more' => 'Mer', + 'no_data' => 'Ingen data hittades!', + 'close' => 'Stäng', + 'edit' => 'Redigera', + 'copy' => 'Kopiera', + 'copied' => 'Kopierad!', + 'home' => 'Hem', + 'explicit' => 'Uteslutande', + 'powered_by' => 'Drivs av {castopod}', + 'actions' => 'Åtgärder', + 'pageInfo' => 'Sida {currentPage} av {pageCount}', + 'go_back' => 'Gå tillbaka', 'forms' => [ 'editor' => [ - 'write' => 'Write', - 'preview' => 'Preview', - 'help' => 'Powered by markdown', + 'write' => 'Skriv', + 'preview' => 'Förhandsgranska', + 'help' => 'Drivs av markdown', ], 'multiSelect' => [ - 'selectText' => 'Press to select', - 'loadingText' => 'Loading…', - 'noResultsText' => 'No results found', - 'noChoicesText' => 'No choices to choose from', - 'maxItemText' => 'Cannot add more items', + 'selectText' => 'Tryck för att välja', + 'loadingText' => 'Laddar…', + 'noResultsText' => 'Hittade inga resultat', + 'noChoicesText' => 'Inga val att välja mellan', + 'maxItemText' => 'Kan inte lägga till fler objekt', ], - 'upload_file' => 'Upload a file', - 'remote_url' => 'Remote URL', + 'upload_file' => 'Ladda upp en fil', + 'remote_url' => 'Fjärr URL', + 'save' => 'Spara', ], 'play_episode_button' => [ - 'play' => 'Play', - 'playing' => 'Playing', + 'play' => 'Spela', + 'playing' => 'Spelar', ], - 'size_limit' => 'Size limit: {0}.', - 'choose_interact' => 'Choose how to interact', - 'view' => 'View', + 'size_limit' => 'Storleksgräns: {0}.', + 'choose_interact' => 'Välj hur du vill interagera', + 'view' => 'Visa', ]; diff --git a/modules/Admin/Language/sv/Contributor.php b/modules/Admin/Language/sv/Contributor.php deleted file mode 100644 index d0f3b93d..00000000 --- a/modules/Admin/Language/sv/Contributor.php +++ /dev/null @@ -1,41 +0,0 @@ - 'Podcast contributors', - 'view' => "{username}'s contribution to {podcastTitle}", - 'add' => 'Add contributor', - 'add_contributor' => 'Add a contributor for {0}', - 'edit_role' => 'Update role for {0}', - 'edit' => 'Edit', - 'remove' => 'Remove', - 'list' => [ - 'username' => 'Username', - 'role' => 'Role', - ], - 'form' => [ - 'user' => 'User', - 'user_placeholder' => 'Select a user…', - 'role' => 'Role', - 'role_placeholder' => 'Select its role…', - 'submit_add' => 'Add contributor', - 'submit_edit' => 'Update role', - ], - 'roles' => [ - 'podcast_admin' => 'Podcast admin', - ], - 'messages' => [ - 'removeOwnerError' => "You can't remove the podcast owner!", - 'removeSuccess' => - 'You have successfully removed {username} from {podcastTitle}', - 'alreadyAddedError' => - "The contributor you're trying to add has already been added!", - ], -]; diff --git a/modules/Admin/Language/sv/Countries.php b/modules/Admin/Language/sv/Countries.php index 4cd5d9c8..1aed3bb0 100644 --- a/modules/Admin/Language/sv/Countries.php +++ b/modules/Admin/Language/sv/Countries.php @@ -12,253 +12,253 @@ declare(strict_types=1); return [ 'AD' => 'Andorra', - 'AE' => 'United Arab Emirates', + 'AE' => 'Förenade Arabemiraten', 'AF' => 'Afghanistan', - 'AG' => 'Antigua and Barbuda', + 'AG' => 'Antigua och Barbuda', 'AI' => 'Anguilla', - 'AL' => 'Albania', - 'AM' => 'Armenia', + 'AL' => 'Albanien', + 'AM' => 'Armenien', 'AO' => 'Angola', - 'AQ' => 'Antarctica', + 'AQ' => 'Antarktis', 'AR' => 'Argentina', - 'AS' => 'American Samoa', - 'AT' => 'Austria', - 'AU' => 'Australia', + 'AS' => 'Amerikanska Samoaöarna', + 'AT' => 'Österrike', + 'AU' => 'Australien', 'AW' => 'Aruba', - 'AX' => 'Åland Islands', - 'AZ' => 'Azerbaijan', - 'BA' => 'Bosnia and Herzegovina', + 'AX' => 'Åland', + 'AZ' => 'Azerbajdzjan', + 'BA' => 'Bosnien och Hercegovina', 'BB' => 'Barbados', 'BD' => 'Bangladesh', - 'BE' => 'Belgium', + 'BE' => 'Belgien', 'BF' => 'Burkina Faso', - 'BG' => 'Bulgaria', + 'BG' => 'Bulgarien', 'BH' => 'Bahrain', 'BI' => 'Burundi', 'BJ' => 'Benin', 'BL' => 'Saint Barthélemy', 'BM' => 'Bermuda', 'BN' => 'Brunei Darussalam', - 'BO' => 'Bolivia, Plurinational State of', - 'BQ' => 'Bonaire, Sint Eustatius and Saba', - 'BR' => 'Brazil', + 'BO' => 'Bolivia', + 'BQ' => 'Bonaire', + 'BR' => 'Brasilien', 'BS' => 'Bahamas', 'BT' => 'Bhutan', - 'BV' => 'Bouvet Island', + 'BV' => 'Bouvetön', 'BW' => 'Botswana', - 'BY' => 'Belarus', + 'BY' => 'Vitryssland', 'BZ' => 'Belize', - 'CA' => 'Canada', - 'CC' => 'Cocos (Keeling) Islands', - 'CD' => 'Congo, the Democratic Republic of the', - 'CF' => 'Central African Republic', - 'CG' => 'Congo', - 'CH' => 'Switzerland', - 'CI' => "Côte d'Ivoire", - 'CK' => 'Cook Islands', + 'CA' => 'Kanada', + 'CC' => 'Kokosöarna', + 'CD' => 'Kongo, Demokratiska republiken Kongo', + 'CF' => 'Centralafrikanska republiken', + 'CG' => 'Kongo', + 'CH' => 'Schweiz', + 'CI' => "Elfenbenskusten", + 'CK' => 'Cooköarna', 'CL' => 'Chile', - 'CM' => 'Cameroon', - 'CN' => 'China', + 'CM' => 'Kamerun', + 'CN' => 'Kina', 'CO' => 'Colombia', 'CR' => 'Costa Rica', - 'CU' => 'Cuba', - 'CV' => 'Cape Verde', - 'CW' => 'Curaçao', - 'CX' => 'Christmas Island', - 'CY' => 'Cyprus', - 'CZ' => 'Czech Republic', - 'DE' => 'Germany', + 'CU' => 'Kuba', + 'CV' => 'Kap Verde', + 'CW' => 'Curacao', + 'CX' => 'Julön', + 'CY' => 'Cypern', + 'CZ' => 'Tjeckien', + 'DE' => 'Tyskland', 'DJ' => 'Djibouti', - 'DK' => 'Denmark', + 'DK' => 'Danmark', 'DM' => 'Dominica', - 'DO' => 'Dominican Republic', - 'DZ' => 'Algeria', + 'DO' => 'Dominikanska republiken', + 'DZ' => 'Algeriet', 'EC' => 'Ecuador', - 'EE' => 'Estonia', - 'EG' => 'Egypt', - 'EH' => 'Western Sahara', + 'EE' => 'Estland', + 'EG' => 'Egypten', + 'EH' => 'Västsahara', 'ER' => 'Eritrea', - 'ES' => 'Spain', - 'ET' => 'Ethiopia', + 'ES' => 'Spanien', + 'ET' => 'Etiopien', 'FI' => 'Finland', 'FJ' => 'Fiji', - 'FK' => 'Falkland Islands (Malvinas)', - 'FM' => 'Micronesia, Federated States of', - 'FO' => 'Faroe Islands', - 'FR' => 'France', + 'FK' => 'Falklandsöarna (Malvinas)', + 'FM' => 'Mikronesien, Federerade Stater', + 'FO' => 'Färöarna', + 'FR' => 'Frankrike', 'GA' => 'Gabon', - 'GB' => 'United Kingdom', + 'GB' => 'Storbritannien', 'GD' => 'Grenada', - 'GE' => 'Georgia', - 'GF' => 'French Guiana', + 'GE' => 'Georgien', + 'GF' => 'Franska Guyana', 'GG' => 'Guernsey', 'GH' => 'Ghana', 'GI' => 'Gibraltar', - 'GL' => 'Greenland', + 'GL' => 'Grönland', 'GM' => 'Gambia', 'GN' => 'Guinea', 'GP' => 'Guadeloupe', 'GQ' => 'Equatorial Guinea', - 'GR' => 'Greece', - 'GS' => 'South Georgia and the South Sandwich Islands', + 'GR' => 'Grekland', + 'GS' => 'Sydgeorgien och Sydsandwichöarna', 'GT' => 'Guatemala', 'GU' => 'Guam', 'GW' => 'Guinea-Bissau', 'GY' => 'Guyana', - 'HK' => 'Hong Kong', - 'HM' => 'Heard Island and McDonald Islands', + 'HK' => 'Hongkong', + 'HM' => 'Heard Island och McDonaldsöarna', 'HN' => 'Honduras', - 'HR' => 'Croatia', + 'HR' => 'Kroatien', 'HT' => 'Haiti', - 'HU' => 'Hungary', - 'ID' => 'Indonesia', - 'IE' => 'Ireland', + 'HU' => 'Ungern', + 'ID' => 'Indonesien', + 'IE' => 'Irland', 'IL' => 'Israel', 'IM' => 'Isle of Man', - 'IN' => 'India', - 'IO' => 'British Indian Ocean Territory', - 'IQ' => 'Iraq', - 'IR' => 'Iran, Islamic Republic of', - 'IS' => 'Iceland', - 'IT' => 'Italy', + 'IN' => 'Indien', + 'IO' => 'Brittiska territoriet i Indiska oceanen', + 'IQ' => 'Irak', + 'IR' => 'Iran, Islamiska republiken av', + 'IS' => 'Island', + 'IT' => 'Italien', 'JE' => 'Jersey', 'JM' => 'Jamaica', - 'JO' => 'Jordan', + 'JO' => 'Jordanien', 'JP' => 'Japan', 'KE' => 'Kenya', 'KG' => 'Kyrgyzstan', - 'KH' => 'Cambodia', + 'KH' => 'Kambodja', 'KI' => 'Kiribati', - 'KM' => 'Comoros', - 'KN' => 'Saint Kitts and Nevis', - 'KP' => "Korea, Democratic People's Republic of", - 'KR' => 'Korea, Republic of', + 'KM' => 'Komorerna', + 'KN' => 'Saint Kitts och Nevis', + 'KP' => "Nordkorea", + 'KR' => 'Sydkorea', 'KW' => 'Kuwait', - 'KY' => 'Cayman Islands', - 'KZ' => 'Kazakhstan', - 'LA' => "Lao People's Democratic Republic", - 'LB' => 'Lebanon', - 'LC' => 'Saint Lucia', + 'KY' => 'Caymanöarna', + 'KZ' => 'Kazakstan', + 'LA' => "Laos", + 'LB' => 'Libanon', + 'LC' => 'Sankt Lucia', 'LI' => 'Liechtenstein', 'LK' => 'Sri Lanka', - 'LR' => 'Liberia', + 'LR' => 'Liberien', 'LS' => 'Lesotho', - 'LT' => 'Lithuania', + 'LT' => 'Litauen', 'LU' => 'Luxembourg', - 'LV' => 'Latvia', - 'LY' => 'Libya', - 'MA' => 'Morocco', + 'LV' => 'Lettland', + 'LY' => 'Libyen', + 'MA' => 'Marocko', 'MC' => 'Monaco', - 'MD' => 'Moldova, Republic of', + 'MD' => 'Moldavien', 'ME' => 'Montenegro', - 'MF' => 'Saint Martin (French part)', - 'MG' => 'Madagascar', - 'MH' => 'Marshall Islands', - 'MK' => 'Macedonia, the Former Yugoslav Republic of', + 'MF' => 'Saint Martin', + 'MG' => 'Madagaskar', + 'MH' => 'Marshallöarna', + 'MK' => 'Makedonien', 'ML' => 'Mali', 'MM' => 'Myanmar', - 'MN' => 'Mongolia', + 'MN' => 'Mongoliet', 'MO' => 'Macao', - 'MP' => 'Northern Mariana Islands', + 'MP' => 'Nordmarianerna', 'MQ' => 'Martinique', 'MR' => 'Mauritania', 'MS' => 'Montserrat', 'MT' => 'Malta', 'MU' => 'Mauritius', - 'MV' => 'Maldives', + 'MV' => 'Maldiverna', 'MW' => 'Malawi', - 'MX' => 'Mexico', + 'MX' => 'Mexiko', 'MY' => 'Malaysia', - 'MZ' => 'Mozambique', - 'N/A' => 'Not Applicable (local IP…)', + 'MZ' => 'Moçambique', + 'N/A' => 'Ej tillämplig (lokal IP…)', 'NA' => 'Namibia', - 'NC' => 'New Caledonia', + 'NC' => 'Nya Kaledonien', 'NE' => 'Niger', - 'NF' => 'Norfolk Island', + 'NF' => 'Norfolkön', 'NG' => 'Nigeria', 'NI' => 'Nicaragua', - 'NL' => 'Netherlands', - 'NO' => 'Norway', + 'NL' => 'Nederländerna', + 'NO' => 'Norge', 'NP' => 'Nepal', 'NR' => 'Nauru', 'NU' => 'Niue', - 'NZ' => 'New Zealand', + 'NZ' => 'Nya Zeeland', 'OM' => 'Oman', 'PA' => 'Panama', 'PE' => 'Peru', - 'PF' => 'French Polynesia', - 'PG' => 'Papua New Guinea', - 'PH' => 'Philippines', + 'PF' => 'Franska Polynesien', + 'PG' => 'Papua Nya Guinea', + 'PH' => 'Filippinerna', 'PK' => 'Pakistan', - 'PL' => 'Poland', - 'PM' => 'Saint Pierre and Miquelon', + 'PL' => 'Polen', + 'PM' => 'Saint Pierre och Miquelon', 'PN' => 'Pitcairn', 'PR' => 'Puerto Rico', - 'PS' => 'Palestine, State of', + 'PS' => 'Palestina', 'PT' => 'Portugal', 'PW' => 'Palau', 'PY' => 'Paraguay', 'QA' => 'Qatar', 'RE' => 'Réunion', - 'RO' => 'Romania', - 'RS' => 'Serbia', - 'RU' => 'Russian Federation', + 'RO' => 'Rumänien', + 'RS' => 'Serbien', + 'RU' => 'Ryssland', 'RW' => 'Rwanda', - 'SA' => 'Saudi Arabia', - 'SB' => 'Solomon Islands', - 'SC' => 'Seychelles', + 'SA' => 'Saudiarabien', + 'SB' => 'Salomonöarna', + 'SC' => 'Seychellerna', 'SD' => 'Sudan', - 'SE' => 'Sweden', + 'SE' => 'Sverige', 'SG' => 'Singapore', - 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', - 'SI' => 'Slovenia', - 'SJ' => 'Svalbard and Jan Mayen', - 'SK' => 'Slovakia', + 'SH' => 'Sankta Helena, Ascension och Tristan da Cunha', + 'SI' => 'Slovenien', + 'SJ' => 'Svalbard', + 'SK' => 'Slovakien', 'SL' => 'Sierra Leone', 'SM' => 'San Marino', 'SN' => 'Senegal', 'SO' => 'Somalia', - 'SR' => 'Suriname', - 'SS' => 'South Sudan', - 'ST' => 'Sao Tome and Principe', + 'SR' => 'Surinam', + 'SS' => 'Sydsudan', + 'ST' => 'Sao Tome och Principe', 'SV' => 'El Salvador', - 'SX' => 'Sint Maarten (Dutch part)', - 'SY' => 'Syrian Arab Republic', + 'SX' => 'Sint Maarten (nederländska delen)', + 'SY' => 'Syrien', 'SZ' => 'Swaziland', - 'TC' => 'Turks and Caicos Islands', - 'TD' => 'Chad', - 'TF' => 'French Southern Territories', + 'TC' => 'Turks- och Caicosöarna', + 'TD' => 'Tchad', + 'TF' => 'Franska sydterritorierna', 'TG' => 'Togo', 'TH' => 'Thailand', 'TJ' => 'Tajikistan', - 'TK' => 'Tokelau', - 'TL' => 'Timor-Leste', + 'TK' => 'Tokelauöarna', + 'TL' => 'Östtimor', 'TM' => 'Turkmenistan', - 'TN' => 'Tunisia', + 'TN' => 'Tunisien', 'TO' => 'Tonga', - 'TR' => 'Turkey', - 'TT' => 'Trinidad and Tobago', + 'TR' => 'Turkiet', + 'TT' => 'Trinidad och Tobago', 'TV' => 'Tuvalu', - 'TW' => 'Taiwan, Province of China', - 'TZ' => 'Tanzania, United Republic of', - 'UA' => 'Ukraine', + 'TW' => 'Taiwan', + 'TZ' => 'Tanzania', + 'UA' => 'Ukraina', 'UG' => 'Uganda', - 'UM' => 'United States Minor Outlying Islands', - 'US' => 'United States', + 'UM' => 'Förenta staternas mindre öar i Oceanien och Västindien', + 'US' => 'USA', 'UY' => 'Uruguay', 'UZ' => 'Uzbekistan', - 'VA' => 'Holy See (Vatican City State)', - 'VC' => 'Saint Vincent and the Grenadines', - 'VE' => 'Venezuela, Bolivarian Republic of', - 'VG' => 'Virgin Islands, British', - 'VI' => 'Virgin Islands, U.S.', - 'VN' => 'Viet Nam', + 'VA' => 'Vatikanstaten', + 'VC' => 'Saint Vincent och Grenadinerna', + 'VE' => 'Venezuela', + 'VG' => 'Brittiska Jungfruöarna', + 'VI' => 'Amerikanska Jungfruöarna.', + 'VN' => 'Vietnam', 'VU' => 'Vanuatu', - 'WF' => 'Wallis and Futuna', + 'WF' => 'Wallis- och Futunaöarna', 'WS' => 'Samoa', - 'YE' => 'Yemen', + 'YE' => 'Jemen', 'YT' => 'Mayotte', - 'ZA' => 'South Africa', + 'ZA' => 'Sydafrika', 'ZM' => 'Zambia', 'ZW' => 'Zimbabwe', ]; diff --git a/modules/Admin/Language/sv/Dashboard.php b/modules/Admin/Language/sv/Dashboard.php index 881073fd..cf127945 100644 --- a/modules/Admin/Language/sv/Dashboard.php +++ b/modules/Admin/Language/sv/Dashboard.php @@ -9,20 +9,20 @@ declare(strict_types=1); */ return [ - 'home' => 'Admin dashboard', - 'welcome_message' => 'Welcome to the admin area!', + 'home' => 'Admin kontrollpanel', + 'welcome_message' => 'Välkommen till adminområdet!', 'podcasts' => [ 'title' => 'Podcasts', - 'not_found' => 'No published podcast', - 'last_published' => 'Last published on {lastPublicationDate}', + 'not_found' => 'Ingen publicerad podcast', + 'last_published' => 'Senast publicerad den {lastPublicationDate}', ], 'episodes' => [ - 'title' => 'Episodes', - 'not_found' => 'No published episode', - 'last_published' => 'Last published on {lastPublicationDate}', + 'title' => 'Avsnitt', + 'not_found' => 'Inga publicerade avsnitt', + 'last_published' => 'Senast publicerad den {lastPublicationDate}', ], 'storage' => [ - 'title' => 'Storage', - 'subtitle' => '{totalUploaded} out of {totalStorage}', + 'title' => 'Lagring', + 'subtitle' => '{totalUploaded} av {totalStorage}', ], ]; diff --git a/modules/Admin/Language/sv/Episode.php b/modules/Admin/Language/sv/Episode.php index 91313a7c..38c7b20c 100644 --- a/modules/Admin/Language/sv/Episode.php +++ b/modules/Admin/Language/sv/Episode.php @@ -9,74 +9,76 @@ declare(strict_types=1); */ return [ - 'season' => 'Season {seasonNumber}', + 'season' => 'Säsong {seasonNumber}', 'season_abbr' => 'S{seasonNumber}', - 'number' => 'Episode {episodeNumber}', - 'number_abbr' => 'Ep. {episodeNumber}', - 'season_episode' => 'Season {seasonNumber} episode {episodeNumber}', - 'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}', + 'number' => 'Avsnitt {episodeNumber}', + 'number_abbr' => 'Av. {episodeNumber}', + 'season_episode' => 'Säsong {seasonNumber} avsnitt {episodeNumber}', + 'season_episode_abbr' => 'S{seasonNumber}A{episodeNumber}', 'number_of_comments' => '{numberOfComments, plural, - one {# comment} - other {# comments} + one {# kommentar} + other {# kommentarer} }', - 'all_podcast_episodes' => 'All podcast episodes', - 'back_to_podcast' => 'Go back to podcast', - 'edit' => 'Edit', - 'publish' => 'Publish', - 'publish_edit' => 'Edit publication', - 'publish_date_edit' => 'Edit publication date', - 'unpublish' => 'Unpublish', - 'publish_error' => 'Episode is already published.', - 'publish_edit_error' => 'Episode is already published.', - 'publish_cancel_error' => 'Episode is already published.', - 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', - 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', - 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', - 'unpublish_error' => 'Episode is not published.', - 'delete' => 'Delete', - 'go_to_page' => 'Go to page', - 'create' => 'Add an episode', + 'all_podcast_episodes' => 'Alla podcast avsnitt', + 'back_to_podcast' => 'Gå tillbaka till podcasten', + 'edit' => 'Redigera', + 'preview' => 'Preview', + 'publish' => 'Publicera', + 'publish_edit' => 'Redigera publikation', + 'publish_date_edit' => 'Redigera publiceringsdatum', + 'unpublish' => 'Avpublicera', + 'publish_error' => 'Avsnittet är redan publicerat.', + 'publish_edit_error' => 'Avsnittet är redan publicerat.', + 'publish_cancel_error' => 'Avsnittet är redan publicerat.', + 'publish_date_edit_error' => 'Avsnittet har inte publicerats ännu, du kan inte redigera dess publiceringsdatum.', + 'publish_date_edit_future_error' => 'Avsnittets publiceringsdatum kan bara ställas in till ett tidigare datum! Om du vill boka om det, avpublicera det först.', + 'publish_date_edit_success' => 'Avsnittets publiceringsdatum har uppdaterats!', + 'unpublish_error' => 'Avsnittet är inte publicerat.', + 'delete' => 'Radera', + 'go_to_page' => 'Gå till sida', + 'create' => 'Lägg till ett avsnitt', 'publication_status' => [ - 'published' => 'Published', - 'with_podcast' => 'Published', - 'scheduled' => 'Scheduled', - 'not_published' => 'Not published', + 'published' => 'Publicerad', + 'with_podcast' => 'Publicerad', + 'scheduled' => 'Schemalagd', + 'not_published' => 'Ej publicerat', ], - 'with_podcast_hint' => 'To be published at the same time as the podcast', + 'with_podcast_hint' => 'Publiceras samtidigt som podcasten', 'list' => [ 'search' => [ - 'placeholder' => 'Search for an episode', - 'clear' => 'Clear search', - 'submit' => 'Search', + 'placeholder' => 'Sök efter ett avsnitt', + 'clear' => 'Rensa sökning', + 'submit' => 'Sök', ], 'number_of_episodes' => '{numberOfEpisodes, plural, - one {# episode} - other {# episodes} + one {# avsnitt} + other {# avsnitt} }', - 'episode' => 'Episode', - 'visibility' => 'Visibility', - 'comments' => 'Comments', - 'actions' => 'Actions', + 'episode' => 'Avsnitt', + 'visibility' => 'Synlighet', + 'downloads' => 'Downloads', + 'comments' => 'Kommentarer', + 'actions' => 'Åtgärder', ], 'messages' => [ - 'createSuccess' => 'Episode has been successfully created!', - 'editSuccess' => 'Episode has been successfully updated!', + 'createSuccess' => 'Avsnittet har skapats!', + 'editSuccess' => 'Avsnittet har uppdaterats!', 'publishSuccess' => '{publication_status, select, - published {Episode successfully published!} - scheduled {Episode publication successfully scheduled!} - with_podcast {This episode will be published at the same time as the podcast.} - other {This episode is not published.} + published {Avsnittet har publicerats!} + scheduled {Avsnittspubliceringen har planerats!} + with_podcast {Detta avsnitt kommer att publiceras samtidigt som podcasten.} + other {Detta avsnitt är inte publicerad.} }', - 'publishCancelSuccess' => 'Episode publication successfully cancelled!', - 'unpublishBeforeDeleteTip' => 'You must unpublish the episode before deleting it.', - 'scheduleDateError' => 'Schedule date must be set!', - 'deletePublishedEpisodeError' => 'Please unpublish the episode before deleting it.', - 'deleteSuccess' => 'Episode successfully deleted!', - 'deleteError' => 'Failed to delete episode {type, select, + 'publishCancelSuccess' => 'Avsnitt publicering har avbrutits!', + 'unpublishBeforeDeleteTip' => 'Du måste avpublicera avsnittet innan du tar bort det.', + 'scheduleDateError' => 'Schemaläggningsdatum måste anges!', + 'deletePublishedEpisodeError' => 'Avpublicera avsnittet innan du tar bort det.', + 'deleteSuccess' => 'Avsnittet har tagits bort!', + 'deleteError' => 'Misslyckades att ta bort avsnitt {type, select, transcript {transcript} - chapters {chapters} - image {cover} - audio {audio} + chapters {kapitel} + image {omslag} + audio {ljud} other {media} }.', 'deleteFileError' => 'Failed to delete {type, select, @@ -85,129 +87,139 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', - 'sameSlugError' => 'An episode with the chosen slug already exists.', + } file {file_key}. You may manually remove it from your disk.', + 'sameSlugError' => 'Ett avsnitt med den valda slug finns redan.', ], 'form' => [ 'file_size_error' => - 'Your file size is too big! Max size is {0}. Increase the `memory_limit`, `upload_max_filesize` and `post_max_size` values in your php configuration file then restart your web server to upload your file.', - 'audio_file' => 'Audio file', - 'audio_file_hint' => 'Choose an .mp3 or .m4a audio file.', - 'info_section_title' => 'Episode info', - 'cover' => 'Episode cover', + 'Din filstorlek är för stor! Max storlek är {0}. Öka värdena `memory_limit`, `upload_max_filesize` och `post_max_size` i din php-konfigurationsfil och starta sedan om din webbserver för att ladda upp filen.', + 'audio_file' => 'Ljudfil', + 'audio_file_hint' => 'Välj en. mp3 eller . m4a ljudfil.', + 'info_section_title' => 'Avsnitt information', + 'cover' => 'Avsnitt omslag', 'cover_hint' => - 'If you do not set a cover, the podcast cover will be used instead.', - 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', - 'title' => 'Title', + 'Om du inte sätter ett omslag kommer podcast-omslaget att användas istället.', + 'cover_size_hint' => 'Omslaget måste vara fyrkantigt och minst 1400px brett och högt.', + 'title' => 'Rubrik', 'title_hint' => - 'Should contain a clear and concise episode name. Do not specify the episode or season numbers here.', - 'permalink' => 'Permalink', - 'season_number' => 'Season', - 'episode_number' => 'Episode', + 'Bör innehålla ett tydligt och koncist avsnittsnamn. Ange inte avsnitt eller säsongsnummer här.', + 'permalink' => 'Permalänk', + 'season_number' => 'Säsong', + 'episode_number' => 'Avsnitt', 'type' => [ - 'label' => 'Type', + 'label' => 'Typ', 'full' => 'Full', - 'full_hint' => 'Complete content (the episode)', + 'full_hint' => 'Komplett innehåll (avsnittet)', 'trailer' => 'Trailer', - 'trailer_hint' => 'Short, promotional piece of content that represents a preview of the current show', + 'trailer_hint' => 'Kort, PR-del av innehåll som representerar en förhandsvisning av den aktuella serien', 'bonus' => 'Bonus', - 'bonus_hint' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show', + 'bonus_hint' => 'Extra innehåll för showen (till exempel bakom scenens info eller intervjuer med cast) eller korspremiellt innehåll för en annan show', ], 'premium_title' => 'Premium', - 'premium' => 'Episode must be accessible to premium subscribers only', + 'premium' => 'Avsnitt måste endast vara tillgängligt för premiumprenumeranter', 'parental_advisory' => [ - 'label' => 'Parental advisory', - 'hint' => 'Does the episode contain explicit content?', - 'undefined' => 'undefined', - 'clean' => 'Clean', - 'explicit' => 'Explicit', + 'label' => 'Föräldrarådgivande', + 'hint' => 'Innehåller avsnittet olämpligt innehåll?', + 'undefined' => 'odefinierad', + 'clean' => 'Ren', + 'explicit' => 'Uteslutande', ], - 'show_notes_section_title' => 'Show notes', + 'show_notes_section_title' => 'Visa anteckningar', 'show_notes_section_subtitle' => - 'Up to 4000 characters, be clear and concise. Show notes help potential listeners in finding the episode.', - 'description' => 'Description', - 'description_footer' => 'Description footer', + 'Upp till 4000 tecken, var tydlig och koncis. Visa anteckningar hjälper potentiella lyssnare att hitta avsnittet.', + 'description' => 'Beskrivning', + 'description_footer' => 'Beskrivning sidfot', 'description_footer_hint' => - 'This text is added at the end of each episode description, it is a good place to input your social links for example.', - 'additional_files_section_title' => 'Additional files', + 'Denna text läggs till i slutet av varje avsnitt beskrivning, det är ett bra ställe att skriva in dina sociala länkar till exempel.', + 'additional_files_section_title' => 'Ytterligare filer', 'additional_files_section_subtitle' => - 'These files may be used by other platforms to provide better experience to your audience. See the {podcastNamespaceLink} for more information.', - 'location_section_title' => 'Location', - 'location_section_subtitle' => 'What place is this episode about?', - 'location_name' => 'Location name or address', - 'location_name_hint' => 'This can be a real or fictional location', - 'transcript' => 'Transcript (subtitles / closed captions)', - 'transcript_hint' => 'Only .srt are allowed.', - 'transcript_download' => 'Download transcript', - 'transcript_file' => 'Transcript file (.srt)', - 'transcript_remote_url' => 'Remote url for transcript', - 'transcript_file_delete' => 'Delete transcript file', - 'chapters' => 'Chapters', - 'chapters_hint' => 'File must be in JSON Chapters format.', - 'chapters_download' => 'Download chapters', - 'chapters_file' => 'Chapters file', - 'chapters_remote_url' => 'Remote url for chapters file', - 'chapters_file_delete' => 'Delete chapters file', - 'advanced_section_title' => 'Advanced Parameters', + 'Dessa filer kan användas av andra plattformar för att ge bättre upplevelse till din publik. Se {podcastNamespaceLink} för mer information.', + 'location_section_title' => 'Plats', + 'location_section_subtitle' => 'Vilken plats handlar detta avsnitt om?', + 'location_name' => 'Platsnamn eller adress', + 'location_name_hint' => 'Detta kan vara en verklig eller fiktiv plats', + 'transcript' => 'Avskrift (undertexter / undertexter)', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', + 'transcript_download' => 'Ladda ner avskrift', + 'transcript_file' => 'Transcript file (.srt or .vtt)', + 'transcript_remote_url' => 'Fjärr-URL för avskrift', + 'transcript_file_delete' => 'Ta bort avskrift', + 'chapters' => 'Kapitel', + 'chapters_hint' => 'Filen måste vara i JSON Kapitelformat.', + 'chapters_download' => 'Ladda ner kapitel', + 'chapters_file' => 'Kapitel fil', + 'chapters_remote_url' => 'Fjärr-URL för kapitelfil', + 'chapters_file_delete' => 'Ta bort kapitelfil', + 'advanced_section_title' => 'Avancerade parametrar', 'advanced_section_subtitle' => - 'If you need RSS tags that Castopod does not handle, set them here.', - 'custom_rss' => 'Custom RSS tags for the episode', - 'custom_rss_hint' => 'This will be injected within the ❬item❭ tag.', - 'block' => 'Episode should be hidden from public catalogues', + 'Om du behöver RSS-taggar som Castopod inte hanterar, ställ in dem här.', + 'custom_rss' => 'Anpassade RSS-taggar för avsnittet', + 'custom_rss_hint' => 'Detta kommer att injiceras inom item taggen.', + 'block' => 'Avsnitt bör döljas från offentliga kataloger', 'block_hint' => - 'The episode show or hide status: toggling this on prevents the episode from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', - 'submit_create' => 'Create episode', - 'submit_edit' => 'Save episode', + 'Avsnitten visa eller dölja status: växla detta hindrar avsnitten från att visas i Apple Podcasts, Google Podcasts, och alla tredjepartsappar som drar program från dessa kataloger. (Inte garanterat)', + 'submit_create' => 'Skapa avsnitt', + 'submit_edit' => 'Spara avsnitt', ], 'publish_form' => [ - 'back_to_episode_dashboard' => 'Back to episode dashboard', - 'post' => 'Your announcement post', + 'back_to_episode_dashboard' => 'Tillbaka till avsnittets instrumentpanel', + 'post' => 'Ditt meddelande inlägg', 'post_hint' => - "Write a message to announce the publication of your episode. The message will be broadcasted to all your followers in the fediverse and be featured in your podcast's homepage.", - 'message_placeholder' => 'Write your message…', - 'publication_date' => 'Publication date', + "Skriv ett meddelande för att meddela publiceringen av ditt avsnitt. Meddelandet kommer att sändas till alla dina följare i fediverse och presenteras på din podcasts hemsida.", + 'message_placeholder' => 'Skriv ditt meddelande…', + 'publication_date' => 'Publiceringsdatum', 'publication_method' => [ - 'now' => 'Now', - 'schedule' => 'Schedule', - 'with_podcast' => 'Publish alongside podcast', + 'now' => 'Nu', + 'schedule' => 'Schemalägg', + 'with_podcast' => 'Publicera tillsammans med podcast', ], - 'scheduled_publication_date' => 'Scheduled publication date', - 'scheduled_publication_date_clear' => 'Clear publication date', + 'scheduled_publication_date' => 'Planerat publiceringsdatum', + 'scheduled_publication_date_clear' => 'Rensa publiceringsdatum', 'scheduled_publication_date_hint' => - 'You can schedule the episode release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', - 'submit' => 'Publish', - 'submit_edit' => 'Edit publication', - 'cancel_publication' => 'Cancel publication', - 'message_warning' => 'You did not write a message for your announcement post!', - 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your episode.', - 'message_warning_submit' => 'Publish anyways', + 'Du kan schemalägga avsnittet genom att ställa in ett framtida publiceringsdatum. Detta fält måste formateras som ÅÅÅ-MM-DD HH:mm', + 'submit' => 'Publicera', + 'submit_edit' => 'Redigera publikation', + 'cancel_publication' => 'Avbryt publicering', + 'message_warning' => 'Du skrev inte ett meddelande för ditt tillkännagivande!', + 'message_warning_hint' => 'Att ha ett meddelande ökar socialt engagemang, vilket resulterar i en bättre synlighet för ditt avsnitt.', + 'message_warning_submit' => 'Publicera ändå', ], 'publish_date_edit_form' => [ - 'new_publication_date' => 'New publication date', - 'new_publication_date_hint' => 'Must be set to a past date.', - 'submit' => 'Edit publication date', + 'new_publication_date' => 'Nytt publiceringsdatum', + 'new_publication_date_hint' => 'Måste vara inställd till ett tidigare datum.', + 'submit' => 'Redigera publiceringsdatum', ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", - 'understand' => 'I understand, I want to unpublish the episode', - 'submit' => 'Unpublish', + "Avpublicera avsnittet kommer att ta bort alla kommentarer och inlägg som är associerade med det och ta bort det från podcastens RSS-flöde.", + 'understand' => 'Jag förstår, jag vill avpublicera avsnittet', + 'submit' => 'Avpublicera', ], 'delete_form' => [ 'disclaimer' => - "Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.", - 'understand' => 'I understand, I want to delete the episode', - 'submit' => 'Delete', + "Borttagning av avsnittet kommer att ta bort alla mediefiler, kommentarer, videoklipp och ljudfiler som är associerade med det.", + 'understand' => 'Jag förstår, Jag vill ta bort avsnittet', + 'submit' => 'Radera', ], 'embed' => [ - 'title' => 'Embeddable player', + 'title' => 'Inbäddad spelare', 'label' => - 'Pick a theme color, copy the embeddable player to clipboard, then paste it on your website.', - 'clipboard_iframe' => 'Copy embeddable player to clipboard', - 'clipboard_url' => 'Copy address to clipboard', - 'dark' => 'Dark', - 'dark-transparent' => 'Dark transparent', - 'light' => 'Light', - 'light-transparent' => 'Light transparent', + 'Välj ett tema färg, kopiera den inbäddade spelaren till urklipp, sedan klistra in den på din webbplats.', + 'clipboard_iframe' => 'Kopiera inbäddbar spelare till urklipp', + 'clipboard_url' => 'Kopiera adress till urklipp', + 'dark' => 'Mörk', + 'dark-transparent' => 'Mörk transparent', + 'light' => 'Ljust', + 'light-transparent' => 'Ljus transparent', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', ], ]; diff --git a/modules/Admin/Language/sv/EpisodeNavigation.php b/modules/Admin/Language/sv/EpisodeNavigation.php index 1406e301..59f92a52 100644 --- a/modules/Admin/Language/sv/EpisodeNavigation.php +++ b/modules/Admin/Language/sv/EpisodeNavigation.php @@ -9,15 +9,15 @@ declare(strict_types=1); */ return [ - 'go_to_page' => 'View episode page', - 'dashboard' => 'Episode dashboard', - 'episode-view' => 'Home', - 'episode-edit' => 'Edit episode', - 'episode-persons-manage' => 'Manage persons', - 'embed-add' => 'Embeddable player', - 'clips' => 'Clips', - 'video-clips-list' => 'Video clips', - 'video-clips-create' => 'New video clip', - 'soundbites-list' => 'Soundbites', - 'soundbites-create' => 'New soundbite', + 'go_to_page' => 'Visa avsnittssida', + 'dashboard' => 'Avsnittets instrumentpanel', + 'episode-view' => 'Hem', + 'episode-edit' => 'Redigera avsnitt', + 'episode-persons-manage' => 'Hantera personer', + 'embed-add' => 'Inbäddad spelare', + 'clips' => 'Klipp', + 'video-clips-list' => 'Videoklipp', + 'video-clips-create' => 'Nytt videoklipp', + 'soundbites-list' => 'Ljudklipp', + 'soundbites-create' => 'Ny ljudbit', ]; diff --git a/modules/Admin/Language/sv/Fediverse.php b/modules/Admin/Language/sv/Fediverse.php index 0e4ca66d..18a1b209 100644 --- a/modules/Admin/Language/sv/Fediverse.php +++ b/modules/Admin/Language/sv/Fediverse.php @@ -10,23 +10,23 @@ declare(strict_types=1); return [ 'messages' => [ - 'actorNotFound' => 'The account could not be found!', - 'blockActorSuccess' => '{actor} has been blocked!', - 'unblockActorSuccess' => 'Actor has been unblocked!', - 'blockDomainSuccess' => '{domain} has been blocked!', - 'unblockDomainSuccess' => '{domain} has been unblocked!', + 'actorNotFound' => 'Det gick inte att hitta kontot!', + 'blockActorSuccess' => '{actor} har blockerats!', + 'unblockActorSuccess' => 'Aktören har blivit avblockerad!', + 'blockDomainSuccess' => '{domain} har blockerats!', + 'unblockDomainSuccess' => '{domain} har blivit avblockerad!', ], - 'blocked_actors' => 'Blocked accounts', - 'blocked_domains' => 'Blocked domains', + 'blocked_actors' => 'Blockerade konton', + 'blocked_domains' => 'Blockerade domäner', 'block_lists_form' => [ - 'handle' => 'Account handle', - 'handle_hint' => 'Input @username@domain account.', - 'domain' => 'Domain name', - 'submit' => 'Block!', + 'handle' => 'Hantera konto', + 'handle_hint' => 'Skriv in @användarnamn@domänkonto.', + 'domain' => 'Domännamn', + 'submit' => 'Blockera!', ], 'list' => [ - 'actor' => 'Account', - 'domain' => 'Domain name', - 'unblock' => 'Unblock', + 'actor' => 'Konto', + 'domain' => 'Domännamn', + 'unblock' => 'Avblockera', ], ]; diff --git a/modules/Admin/Language/sv/Home.php b/modules/Admin/Language/sv/Home.php index 3ff4c04d..493b5d18 100644 --- a/modules/Admin/Language/sv/Home.php +++ b/modules/Admin/Language/sv/Home.php @@ -9,6 +9,6 @@ declare(strict_types=1); */ return [ - 'all_podcasts' => 'All podcasts', - 'no_podcast' => 'No podcast found', + 'all_podcasts' => 'Alla podcasts', + 'no_podcast' => 'Ingen podcast hittades', ]; diff --git a/modules/Admin/Language/sv/Install.php b/modules/Admin/Language/sv/Install.php index 36e373a2..9e7de812 100644 --- a/modules/Admin/Language/sv/Install.php +++ b/modules/Admin/Language/sv/Install.php @@ -9,53 +9,53 @@ declare(strict_types=1); */ return [ - 'manual_config' => 'Manual configuration', + 'manual_config' => 'Manuell konfiguration', 'manual_config_subtitle' => - 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'Skapa en \'.env\' fil med dina inställningar och uppdatera sidan för att fortsätta installationen.', 'form' => [ - 'instance_config' => 'Instance configuration', - 'hostname' => 'Hostname', - 'media_base_url' => 'Media base URL', + 'instance_config' => 'Konfiguration av instans', + 'hostname' => 'Servernamn', + 'media_base_url' => 'Bas-URL för media', 'media_base_url_hint' => - 'If you use a CDN and/or an external analytics service, you may set them here.', + 'Om du använder en CDN och/eller en extern analystjänst kan du ställa in dem här.', 'admin_gateway' => 'Admin gateway', 'admin_gateway_hint' => - 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'Rutten för att komma åt adminområdet (t.ex. https://example.com/cp-admin). Det är som standard inställt som cp-admin, vi rekommenderar att du ändrar det av säkerhetsskäl.', 'auth_gateway' => 'Auth gateway', 'auth_gateway_hint' => - 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', - 'database_config' => 'Database configuration', + 'Rutten för att komma åt autentiseringssidorna (t.ex. https://example.com/cp-auth). Den är som standard inställd som cp-auth, vi rekommenderar att du ändrar den av säkerhetsskäl.', + 'database_config' => 'Databas konfiguration', 'database_config_hint' => - 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', - 'db_hostname' => 'Database hostname', - 'db_name' => 'Database name', - 'db_username' => 'Database username', - 'db_password' => 'Database password', - 'db_prefix' => 'Database prefix', + 'Castopod måste ansluta till din MySQL (eller MariaDB) databas. Om du inte har dessa nödvändiga uppgifter, kontakta din serveradministratör.', + 'db_hostname' => 'Databasens värdnamn', + 'db_name' => 'Databasnamn', + 'db_username' => 'Användarnamn till databasen', + 'db_password' => 'Databasens lösenord', + 'db_prefix' => 'Databas prefix', 'db_prefix_hint' => - "The prefix of the Castopod table names, leave as is if you don't know what it means.", - 'cache_config' => 'Cache configuration', + "Prefixet för Castopod tabellnamn, lämna som om du inte vet vad det betyder.", + 'cache_config' => 'Cache-konfiguration', 'cache_config_hint' => - 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'Välj önskad cachehanterare. Lämna det som standardvärde om du inte har någon aning om vad det innebär.', 'cache_handler' => 'Cache handler', 'cacheHandlerOptions' => [ - 'file' => 'File', + 'file' => 'Fil', 'redis' => 'Redis', 'predis' => 'Predis', ], - 'next' => 'Next', - 'submit' => 'Finish install', - 'create_superadmin' => 'Create your superadmin account', - 'email' => 'Email', - 'username' => 'Username', - 'password' => 'Password', + 'next' => 'Nästa', + 'submit' => 'Slutför installationen', + 'create_superadmin' => 'Skapa ditt superadministratörskonto', + 'email' => 'Epost', + 'username' => 'Användarnamn', + 'password' => 'Lösenord', ], 'messages' => [ 'createSuperAdminSuccess' => - 'Your superadmin account has been created successfully. Login to start podcasting!', + 'Ditt superadministratörskonto har skapats. Logga in för att starta podcasting!', 'databaseConnectError' => - 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'Castopod kunde inte ansluta till din databas. Redigera din databaskonfiguration och försök igen.', 'writeError' => - "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + "Kunde inte skapa/skriva `.env`-filen. Du måste skapa den manuellt genom att följa filmallen `.env.exempel` i Castopod-paketet.", ], ]; diff --git a/modules/Admin/Language/sv/Navigation.php b/modules/Admin/Language/sv/Navigation.php index 68d4609d..4e710a45 100644 --- a/modules/Admin/Language/sv/Navigation.php +++ b/modules/Admin/Language/sv/Navigation.php @@ -9,33 +9,36 @@ declare(strict_types=1); */ return [ - 'toggle_sidebar' => 'Toggle sidebar', - 'go_to_website' => 'Go to website', - 'go_to_admin' => 'Go to admin', - 'dashboard' => 'Dashboard', - 'admin' => 'Home', + 'toggle_sidebar' => 'Växla sidofält', + 'go_to_website' => 'Gå till webbsidan', + 'go_to_admin' => 'Gå till admin', + 'not-authorized' => 'Not authorized', + 'dashboard' => 'Översiktspanel', + 'admin' => 'Hem', 'podcasts' => 'Podcasts', - 'podcast-list' => 'All podcasts', - 'podcast-create' => 'New podcast', - 'podcast-import' => 'Import a podcast', - 'persons' => 'Persons', - 'person-list' => 'All persons', - 'person-create' => 'New person', + 'podcast-list' => 'Alla podcasts', + 'podcast-create' => 'Ny podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', + 'persons' => 'Personer', + 'person-list' => 'Alla personer', + 'person-create' => 'Ny person', 'fediverse' => 'Fediverse', - 'fediverse-blocked-actors' => 'Blocked accounts', - 'fediverse-blocked-domains' => 'Blocked domains', - 'users' => 'Users', - 'user-list' => 'All users', - 'user-create' => 'New user', - 'pages' => 'Pages', - 'page-list' => 'All pages', - 'page-create' => 'New Page', - 'settings' => 'Settings', - 'settings-general' => 'General', - 'settings-theme' => 'Theme', + 'fediverse-blocked-actors' => 'Blockerade konton', + 'fediverse-blocked-domains' => 'Blockerade domäner', + 'users' => 'Användare', + 'user-list' => 'Alla användare', + 'user-create' => 'Ny användare', + 'pages' => 'Sidor', + 'page-list' => 'Alla sidor', + 'page-create' => 'Ny sida', + 'settings' => 'Inställningar', + 'settings-general' => 'Allmänt', + 'settings-theme' => 'Tema', + 'admin-about' => 'Om', 'account' => [ - 'my-account' => 'My account', - 'change-password' => 'Change password', - 'logout' => 'Logout', + 'my-account' => 'Mitt konto', + 'change-password' => 'Ändra lösenord', + 'logout' => 'Logga ut', ], ]; diff --git a/modules/Admin/Language/sv/Notifications.php b/modules/Admin/Language/sv/Notifications.php index 2b139d51..cacbb79a 100644 --- a/modules/Admin/Language/sv/Notifications.php +++ b/modules/Admin/Language/sv/Notifications.php @@ -9,11 +9,11 @@ declare(strict_types=1); */ return [ - 'title' => 'Notifications', - 'reply' => '{actor_username} replied to your post', - 'favourite' => '{actor_username} favourited your post', - 'reblog' => '{actor_username} shared your post', - 'follow' => '{actor_username} started following you', - 'no_notifications' => 'No notifications', - 'mark_all_as_read' => 'Mark all as read', + 'title' => 'Aviseringar', + 'reply' => '{actor_username} svarade på ditt inlägg', + 'favourite' => '{actor_username} favoriterade ditt inlägg', + 'reblog' => '{actor_username} delade ditt inlägg', + 'follow' => '{actor_username} började följa dig', + 'no_notifications' => 'Inga aviseringar', + 'mark_all_as_read' => 'Markera alla som lästa', ]; diff --git a/modules/Admin/Language/sv/Page.php b/modules/Admin/Language/sv/Page.php index b6f49de5..89f48754 100644 --- a/modules/Admin/Language/sv/Page.php +++ b/modules/Admin/Language/sv/Page.php @@ -9,22 +9,22 @@ declare(strict_types=1); */ return [ - 'back_to_home' => 'Back to home', - 'page' => 'Page', - 'all_pages' => 'All pages', - 'create' => 'New page', - 'go_to_page' => 'Go to page', - 'edit' => 'Edit page', - 'delete' => 'Delete page', + 'back_to_home' => 'Tillbaka till startsidan', + 'page' => 'Sida', + 'all_pages' => 'Alla sidor', + 'create' => 'Ny sida', + 'go_to_page' => 'Gå till sida', + 'edit' => 'Redigera sida', + 'delete' => 'Ta bort sida', 'form' => [ - 'title' => 'Title', - 'permalink' => 'Permalink', - 'content' => 'Content', - 'submit_create' => 'Create page', - 'submit_edit' => 'Save', + 'title' => 'Rubrik', + 'permalink' => 'Permalänk', + 'content' => 'Innehåll', + 'submit_create' => 'Skapa sida', + 'submit_edit' => 'Spara', ], 'messages' => [ - 'createSuccess' => 'The page “{pageTitle}” was created successfully!', - 'editSuccess' => 'The page was successfully updated!', + 'createSuccess' => 'Sidan ”{pageTitle}” skapades framgångsrikt!', + 'editSuccess' => 'Sidan har uppdaterats!', ], ]; diff --git a/modules/Admin/Language/sv/Pager.php b/modules/Admin/Language/sv/Pager.php index e25ee638..32a18e90 100644 --- a/modules/Admin/Language/sv/Pager.php +++ b/modules/Admin/Language/sv/Pager.php @@ -9,13 +9,13 @@ declare(strict_types=1); */ return [ - 'pageNavigation' => 'Page navigation', - 'first' => 'First', - 'previous' => 'Previous', - 'next' => 'Next', - 'last' => 'Last', - 'older' => 'Older', - 'newer' => 'Newer', - 'invalidTemplate' => '{0} is not a valid Pager template.', - 'invalidPaginationGroup' => '{0} is not a valid Pagination group.', + 'pageNavigation' => 'Sidnavigering', + 'first' => 'Första', + 'previous' => 'Föregående', + 'next' => 'Nästa', + 'last' => 'Sista', + 'older' => 'Äldre', + 'newer' => 'Nyare', + 'invalidTemplate' => '{0} är inte en giltig sidmall.', + 'invalidPaginationGroup' => '{0} är inte en giltig sidnumreringsgrupp.', ]; diff --git a/modules/Admin/Language/sv/Person.php b/modules/Admin/Language/sv/Person.php index a652be9f..f5007527 100644 --- a/modules/Admin/Language/sv/Person.php +++ b/modules/Admin/Language/sv/Person.php @@ -9,57 +9,57 @@ declare(strict_types=1); */ return [ - 'persons' => 'Persons', - 'all_persons' => 'All persons', - 'no_person' => 'Nobody found!', - 'create' => 'Create a person', - 'view' => 'View person', - 'edit' => 'Edit person', - 'delete' => 'Delete person', + 'persons' => 'Personer', + 'all_persons' => 'Alla personer', + 'no_person' => 'Ingen hittades!', + 'create' => 'Skapa en person', + 'view' => 'Visa person', + 'edit' => 'Redigera person', + 'delete' => 'Ta bort person', 'messages' => [ - 'createSuccess' => 'Person has been successfully created!', - 'editSuccess' => 'Person has been successfully updated!', - 'deleteSuccess' => 'Person has been removed!', + 'createSuccess' => 'Person har skapats framgångsrikt!', + 'editSuccess' => 'Person har uppdaterats!', + 'deleteSuccess' => 'Person har tagits bort!', ], 'form' => [ 'avatar' => 'Avatar', 'avatar_size_hint' => - 'Avatar must be squared and at least 400px wide and tall.', - 'full_name' => 'Full name', - 'full_name_hint' => 'This is the full name or alias of the person.', - 'unique_name' => 'Unique name', - 'unique_name_hint' => 'Used for URLs', + 'Avatar måste vara kvadratisk och minst 400px bred och hög.', + 'full_name' => 'Fullständigt namn', + 'full_name_hint' => 'Detta är personens fullständiga namn eller alias.', + 'unique_name' => 'Unikt namn', + 'unique_name_hint' => 'Används för URL: er', 'information_url' => 'Information URL', 'information_url_hint' => - 'Url to a relevant resource of information about the person, such as a homepage or third-party profile platform.', - 'submit_create' => 'Create person', - 'submit_edit' => 'Save person', + 'Url till en relevant resurs av information om personen, såsom en hemsida eller en tredje parts profilplattform.', + 'submit_create' => 'Skapa person', + 'submit_edit' => 'Spara person', ], 'podcast_form' => [ - 'title' => 'Manage persons', - 'add_section_title' => 'Add persons to this podcast', - 'add_section_subtitle' => 'You may pick several persons and roles.', - 'persons' => 'Persons', + 'title' => 'Hantera personer', + 'add_section_title' => 'Lägg till personer till denna podcast', + 'add_section_subtitle' => 'Du kan välja flera personer och roller.', + 'persons' => 'Personer', 'persons_hint' => - 'You may select one or several persons with the same roles. You need to create the persons first.', - 'roles' => 'Roles', + 'Du kan välja en eller flera personer med samma roller. Du måste skapa personerna först.', + 'roles' => 'Roller', 'roles_hint' => - 'You may select none, one or several roles for a person.', - 'submit_add' => 'Add person(s)', - 'remove' => 'Remove', + 'Du kan välja ingen, en eller flera roller för en person.', + 'submit_add' => 'Lägg till person(er)', + 'remove' => 'Ta bort', ], 'episode_form' => [ - 'title' => 'Manage persons', - 'add_section_title' => 'Add persons to this episode', - 'add_section_subtitle' => 'You may pick several persons and roles.', - 'persons' => 'Persons', + 'title' => 'Hantera personer', + 'add_section_title' => 'Lägg till personer till detta avsnitt', + 'add_section_subtitle' => 'Du kan välja flera personer och roller.', + 'persons' => 'Personer', 'persons_hint' => - 'You may select one or several persons with the same roles. You need to create the persons first.', - 'roles' => 'Roles', + 'Du kan välja en eller flera personer med samma roller. Du måste skapa personerna först.', + 'roles' => 'Roller', 'roles_hint' => - 'You may select none, one or several roles for a person.', - 'submit_add' => 'Add person(s)', - 'remove' => 'Remove', + 'Du kan välja ingen, en eller flera roller för en person.', + 'submit_add' => 'Lägg till person(er)', + 'remove' => 'Ta bort', ], - 'credits' => 'Credits', + 'credits' => 'Tack till', ]; diff --git a/modules/Admin/Language/sv/Platforms.php b/modules/Admin/Language/sv/Platforms.php index ab17d599..40eab4f8 100644 --- a/modules/Admin/Language/sv/Platforms.php +++ b/modules/Admin/Language/sv/Platforms.php @@ -9,22 +9,35 @@ declare(strict_types=1); */ return [ - 'title' => 'Platforms', - 'home_url' => 'Go to {platformName} website', - 'submit_url' => 'Submit your podcast on {platformName}', - 'visible' => 'Display in podcast homepage?', - 'on_embed' => 'Display on embeddable player?', - 'remove' => 'Remove {platformName}', - 'submit' => 'Save', + 'title' => [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', + 'home_url' => 'Gå till {platformName} webbplats', + 'register' => 'Register', + 'submit_url' => 'Skicka din podcast den {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', + 'visible' => 'Visa på podcastens hemsida?', + 'on_embed' => 'Visa på inbäddbar spelare?', + 'remove' => 'Ta bort {platformName}', + 'submit' => 'Spara', 'messages' => [ - 'updateSuccess' => 'Platform links have been successfully updated!', - 'removeLinkSuccess' => 'The platform link has been removed.', + 'updateSuccess' => 'Plattformslänkarna har uppdaterats!', + 'removeLinkSuccess' => 'Plattformslänken har tagits bort.', 'removeLinkError' => - 'The platform link could not be removed. Try again.', + 'Plattformslänken kunde inte tas bort. Försök igen.', ], 'description' => [ - 'podcasting' => 'The podcast ID on this platform', - 'social' => 'The podcast account ID on this platform', - 'funding' => 'Call to action message', + 'podcasting' => 'Podcast ID på denna plattform', + 'social' => 'Den podcast konto ID på denna plattform', + 'funding' => 'Ring till åtgärdsmeddelande', ], ]; diff --git a/modules/Admin/Language/sv/Podcast.php b/modules/Admin/Language/sv/Podcast.php index 426b763b..7fda701b 100644 --- a/modules/Admin/Language/sv/Podcast.php +++ b/modules/Admin/Language/sv/Podcast.php @@ -9,302 +9,322 @@ declare(strict_types=1); */ return [ - 'all_podcasts' => 'All podcasts', - 'no_podcast' => 'No podcast found!', - 'create' => 'Create podcast', - 'import' => 'Import podcast', - 'new_episode' => 'New Episode', - 'view' => 'View podcast', - 'edit' => 'Edit podcast', - 'publish' => 'Publish podcast', - 'publish_edit' => 'Edit publication', - 'delete' => 'Delete podcast', - 'see_episodes' => 'See episodes', - 'see_contributors' => 'See contributors', - 'go_to_page' => 'Go to page', - 'latest_episodes' => 'Latest episodes', - 'see_all_episodes' => 'See all episodes', - 'draft' => 'Draft', + 'all_podcasts' => 'Alla podcasts', + 'no_podcast' => 'Ingen podcast hittades!', + 'create' => 'Skapa podcast', + 'import' => 'Importera podcast', + 'all_imports' => 'Podcast imports', + 'new_episode' => 'Nytt avsnitt', + 'view' => 'Visa podcast', + 'edit' => 'Redigera podcast', + 'publish' => 'Publicera podcasten', + 'publish_edit' => 'Redigera publikation', + 'delete' => 'Ta bort podcast', + 'see_episodes' => 'Se avsnitt', + 'see_contributors' => 'Se bidragsgivare', + 'monetization_other' => 'Other monetization', + 'go_to_page' => 'Gå till sida', + 'latest_episodes' => 'Senaste avsnitt', + 'see_all_episodes' => 'Se alla avsnitt', + 'draft' => 'Utkast', 'messages' => [ - 'createSuccess' => 'Podcast successfully created!', - 'editSuccess' => 'Podcast has been successfully updated!', - 'importSuccess' => 'Podcast has been successfully imported!', - 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', - 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, - cover {cover} + 'createSuccess' => 'Podcast har skapats!', + 'editSuccess' => 'Podcasten har uppdaterats!', + 'importSuccess' => 'Podcasten har importerats!', + 'deleteSuccess' => 'Podcast @{podcast_handle} har raderats!', + 'deletePodcastMediaError' => 'Kunde inte ta bort podcast {type, select, + cover {omslag} banner {banner} other {media} }.', - 'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select, + 'deleteEpisodeMediaError' => 'Misslyckades att ta bort avsnitt {episode_slug} {type, select, transcript {transcript} - chapters {chapters} - image {cover} - audio {audio} + chapters {kapitel} + image {omslag} + audio {ljud} other {media} }.', - 'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.', - 'podcastFeedUpdateSuccess' => 'Successful update: {number_of_new_episodes, plural, - one {# episode was} - other {# episodes were} - } added to the podcast!', - 'podcastFeedUpToDate' => 'Podcast is already up to date.', - 'podcastNotImported' => 'Podcast could not be updated as it was not imported.', - 'publishError' => 'This podcast is either already published or scheduled for publication.', - 'publishEditError' => 'This podcast is not scheduled for publication.', - 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', - 'scheduleDateError' => 'Schedule date must be set!', + 'deletePodcastMediaFolderError' => 'Det gick inte att ta bort podcast-mediamappen {folder_path}. Du kan manuellt ta bort den från disken.', + 'podcastFeedUpdateSuccess' => 'Lyckad uppdatering: {number_of_new_episodes, plural, + one {# episoden var} + other {# episoder var} + } lades till i podcasten!', + 'podcastFeedUpToDate' => 'Podcasten är redan uppdaterad.', + 'publishError' => 'Denna podcast är antingen redan publicerad eller schemalagd för publicering.', + 'publishEditError' => 'Denna podcast är inte schemalagd för publicering.', + 'publishCancelSuccess' => 'Podcast publicering avbröts!', + 'scheduleDateError' => 'Schemaläggningsdatum måste anges!', ], 'form' => [ - 'identity_section_title' => 'Podcast identity', - 'identity_section_subtitle' => 'These fields allow you to get noticed.', - 'cover' => 'Podcast cover', - 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'identity_section_title' => 'Podcast identitet', + 'identity_section_subtitle' => 'Dessa fält gör att du kan bli uppmärksammad.', + 'fediverse_section_title' => 'Fediverse identity', + + 'cover' => 'Podcast omslag', + 'cover_size_hint' => 'Omslaget måste vara fyrkantigt och minst 1400px brett och högt.', 'banner' => 'Podcast banner', - 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', - 'banner_delete' => 'Delete podcast banner', - 'title' => 'Title', - 'handle' => 'Handle', + 'banner_size_hint' => 'Banner måste ha ett 3:1-förhållande och vara minst 1500px bred.', + 'banner_delete' => 'Ta bort podcast banner', + 'title' => 'Rubrik', + 'handle' => 'Hantera', 'handle_hint' => - 'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.', + 'Används för att identifiera podcasten. Versaler, gemener, siffror och understrykningar accepteras.', 'type' => [ - 'label' => 'Type', + 'label' => 'Typ', 'episodic' => 'Episodic', - 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', - 'serial' => 'Serial', - 'serial_hint' => 'If episodes are intended to be consumed in sequential order. The oldest episodes will be presented first.', + 'episodic_hint' => 'Om avsnitt är avsedda att konsumeras utan någon specifik ordning. Nyaste avsnitt kommer att presenteras först.', + 'serial' => 'Serie', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', ], - 'description' => 'Description', - 'classification_section_title' => 'Classification', + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + ], + 'description' => 'Beskrivning', + 'classification_section_title' => 'Klassificering', 'classification_section_subtitle' => - 'These fields will impact your audience and competition.', - 'language' => 'Language', - 'category' => 'Category', - 'category_placeholder' => 'Select a category…', - 'other_categories' => 'Other categories', + 'Dessa fält kommer att påverka din publik och konkurrens.', + 'language' => 'Språk', + 'category' => 'Kategori', + 'category_placeholder' => 'Välj en kategori…', + 'other_categories' => 'Andra kategorier', 'parental_advisory' => [ - 'label' => 'Parental advisory', - 'hint' => 'Does it contain explicit content?', - 'undefined' => 'undefined', - 'clean' => 'Clean', - 'explicit' => 'Explicit', + 'label' => 'Föräldrarådgivande', + 'hint' => 'Innehåller det olämpligt innehåll?', + 'undefined' => 'odefinierad', + 'clean' => 'Ren', + 'explicit' => 'Uteslutande', ], - 'author_section_title' => 'Author', - 'author_section_subtitle' => 'Who is managing the podcast?', - 'owner_name' => 'Owner name', + 'author_section_title' => 'Författare', + 'author_section_subtitle' => 'Vem hanterar podcasten?', + 'owner_name' => 'Ägarens namn', 'owner_name_hint' => - 'For administrative use only. Visible in the public RSS feed.', - 'owner_email' => 'Owner email', + 'Endast för administrativt bruk. Synlig i det offentliga RSS-flödet.', + 'owner_email' => 'Ägarens e-post', 'owner_email_hint' => - 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', - 'publisher' => 'Publisher', + 'Kommer att användas av de flesta plattformar för att verifiera podcast-ägandet. Synlig i det offentliga RSS-flödet.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', + 'publisher' => 'Utgivare', 'publisher_hint' => - 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', + 'Gruppen som ansvarar för att skapa showen. Ofta hänvisar man till moderbolaget eller nätverket av en podcast. Detta fält är ibland märkt som "Författare".', 'copyright' => 'Copyright', - 'location_section_title' => 'Location', - 'location_section_subtitle' => 'What place is this podcast about?', - 'location_name' => 'Location name or address', - 'location_name_hint' => 'This can be a real place or fictional', - 'monetization_section_title' => 'Monetization', + 'location_section_title' => 'Plats', + 'location_section_subtitle' => 'Vilken plats handlar denna podcast om?', + 'location_name' => 'Platsnamn eller adress', + 'location_name_hint' => 'Detta kan vara en riktig plats eller fiktiv', + 'monetization_section_title' => 'Inkomster', 'monetization_section_subtitle' => - 'Earn money thanks to your audience.', + 'Tjäna pengar tack vare din publik.', 'premium' => 'Premium', - 'premium_by_default' => 'Episodes must be set as premium by default', - 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', - 'payment_pointer' => 'Payment Pointer for Web Monetization', + 'premium_by_default' => 'Avsnitt måste anges som premium som standard', + 'premium_by_default_hint' => 'Podcast avsnitt kommer att markeras som premium som standard. Du kan fortfarande välja att ställa in några avsnitt, trailers eller bonusar som offentliga.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', + 'payment_pointer' => 'Betalning pekare för Web Monetization', 'payment_pointer_hint' => - 'This is your where you will receive money thanks to Web Monetization', - 'advanced_section_title' => 'Advanced Parameters', + 'Detta är din där du kommer att få pengar tack vare Web Monetization', + 'advanced_section_title' => 'Avancerade parametrar', 'advanced_section_subtitle' => - 'If you need RSS tags that Castopod does not handle, set them here.', - 'custom_rss' => 'Custom RSS tags for the podcast', - 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', - 'new_feed_url' => 'New feed URL', - 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', - 'old_feed_url' => 'Old feed URL', - 'update_feed' => 'Update feed', - 'update_feed_tip' => 'Import this podcast\'s latest episodes', - 'partnership' => 'Partnership', + 'Om du behöver RSS-taggar som Castopod inte hanterar, ställ in dem här.', + 'custom_rss' => 'Anpassade RSS-taggar för podcasten', + 'custom_rss_hint' => 'Detta kommer att injiceras i kanal taggen.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', + 'new_feed_url' => 'Ny flödes-URL', + 'new_feed_url_hint' => 'Använd detta fält när du flyttar till en annan domän eller podcast webbhotell. Som standard är värdet inställt på nuvarande RSS-URL om podcasten importeras.', + 'old_feed_url' => 'Gammal flödes-URL', + 'partnership' => 'Samarbete', 'partner_id' => 'ID', - 'partner_link_url' => 'Link URL', - 'partner_image_url' => 'Image URL', - 'partner_id_hint' => 'Your own partner ID', - 'partner_link_url_hint' => 'The generic partner link address', - 'partner_image_url_hint' => 'The generic partner image address', - 'status_section_title' => 'Status', - 'block' => 'Podcast should be hidden from public catalogues', + 'partner_link_url' => 'Länk url', + 'partner_image_url' => 'Bild-URL', + 'partner_id_hint' => 'Din egen partner ID', + 'partner_link_url_hint' => 'Den generiska partnerns länkadress', + 'partner_image_url_hint' => 'Den generiska partnerns bildadress', + 'block' => 'Podcast bör döljas från offentliga kataloger', 'block_hint' => - 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', - 'complete' => 'Podcast will not be having new episodes', - 'lock' => 'Prevent podcast from being copied', + 'Podcasten visar eller dölj status: att växla på detta hindrar hela podcasten från att visas i Apple Podcasts, Google Podcasts, och alla tredjepartsappar som drar program från dessa kataloger. (Inte garanterat)', + 'complete' => 'Podcast kommer inte att ha nya avsnitt', + 'lock' => 'Förhindra podcast från att kopieras', 'lock_hint' => - 'The purpose is to tell other podcast platforms whether they are allowed to import this feed. A value of yes means that any attempt to import this feed into a new platform should be rejected.', - 'submit_create' => 'Create podcast', - 'submit_edit' => 'Save podcast', + 'Syftet är att berätta för andra podcast-plattformar om de får importera detta flöde. Ett värde av ja innebär att varje försök att importera detta flöde till en ny plattform bör avvisas.', + 'submit_create' => 'Skapa podcast', + 'submit_edit' => 'Spara podcast', ], 'category_options' => [ - 'uncategorized' => 'uncategorized', - 'arts' => 'Arts', - 'business' => 'Business', - 'comedy' => 'Comedy', - 'education' => 'Education', - 'fiction' => 'Fiction', - 'government' => 'Government', - 'health_and_fitness' => 'Health & Fitness', - 'history' => 'History', - 'kids_and_family' => 'Kids & Family', - 'leisure' => 'Leisure', - 'music' => 'Music', - 'news' => 'News', - 'religion_and_spirituality' => 'Religion & Spirituality', - 'science' => 'Science', - 'society_and_culture' => 'Society & Culture', - 'sports' => 'Sports', - 'technology' => 'Technology', - 'true_crime' => 'True Crime', - 'tv_and_film' => 'TV & Film', - 'books' => 'Books', + 'uncategorized' => 'okategoriserad', + 'arts' => 'Konst', + 'business' => 'Företagande', + 'comedy' => 'Komedi', + 'education' => 'Utbildning', + 'fiction' => 'Fiktion', + 'government' => 'Myndighet', + 'health_and_fitness' => 'Hälsa & Träning', + 'history' => 'Historia', + 'kids_and_family' => 'Barn & Familj', + 'leisure' => 'Fritid', + 'music' => 'Musik', + 'news' => 'Nyheter', + 'religion_and_spirituality' => 'Religion & Andlighet', + 'science' => 'Vetenskap', + 'society_and_culture' => 'Samhälle & Kultur', + 'sports' => 'Sport', + 'technology' => 'Teknologi', + 'true_crime' => 'Sann brottslighet', + 'tv_and_film' => 'TV & Film', + 'books' => 'Böcker', 'design' => 'Design', - 'fashion_and_beauty' => 'Fashion & Beauty', - 'food' => 'Food', - 'performing_arts' => 'Performing Arts', - 'visual_arts' => 'Visual Arts', - 'careers' => 'Careers', - 'entrepreneurship' => 'Entrepreneurship', - 'investing' => 'Investing', + 'fashion_and_beauty' => 'Mode & Skönhet', + 'food' => 'Mat', + 'performing_arts' => 'Scenkonst', + 'visual_arts' => 'Visuell konst', + 'careers' => 'Karriärer', + 'entrepreneurship' => 'Entreprenörskap', + 'investing' => 'Investering', 'management' => 'Management', - 'marketing' => 'Marketing', - 'non_profit' => 'Non-Profit', - 'comedy_interviews' => 'Comedy Interviews', - 'improv' => 'Improv', + 'marketing' => 'Marknadsföring', + 'non_profit' => 'Ideell organisation', + 'comedy_interviews' => 'Komedi Intervjuer', + 'improv' => 'Förbättra', 'stand_up' => 'Stand-Up', - 'courses' => 'Courses', - 'how_to' => 'How To', - 'language_learning' => 'Language Learning', - 'self_improvement' => 'Self-Improvement', - 'comedy_fiction' => 'Comedy Fiction', + 'courses' => 'Kurser', + 'how_to' => 'Så funkar det', + 'language_learning' => 'Språkinlärning', + 'self_improvement' => 'Självförbättring', + 'comedy_fiction' => 'Komedi fiktion', 'drama' => 'Drama', 'science_fiction' => 'Science Fiction', - 'alternative_health' => 'Alternative Health', - 'fitness' => 'Fitness', - 'medicine' => 'Medicine', - 'mental_health' => 'Mental Health', - 'nutrition' => 'Nutrition', - 'sexuality' => 'Sexuality', - 'education_for_kids' => 'Education for Kids', - 'parenting' => 'Parenting', - 'pets_and_animals' => 'Pets & Animals', - 'stories_for_kids' => 'Stories for Kids', - 'animation_and_manga' => 'Animation & Manga', - 'automotive' => 'Automotive', - 'aviation' => 'Aviation', - 'crafts' => 'Crafts', - 'games' => 'Games', - 'hobbies' => 'Hobbies', - 'home_and_garden' => 'Home & Garden', - 'video_games' => 'Video Games', - 'music_commentary' => 'Music Commentary', - 'music_history' => 'Music History', - 'music_interviews' => 'Music Interviews', - 'business_news' => 'Business News', - 'daily_news' => 'Daily News', - 'entertainment_news' => 'Entertainment News', - 'news_commentary' => 'News Commentary', - 'politics' => 'Politics', - 'sports_news' => 'Sports News', - 'tech_news' => 'Tech News', - 'buddhism' => 'Buddhism', - 'christianity' => 'Christianity', + 'alternative_health' => 'Alternativ Hälsa', + 'fitness' => 'Träning', + 'medicine' => 'Medicin', + 'mental_health' => 'Mental hälsa', + 'nutrition' => 'Näring', + 'sexuality' => 'Sexualitet', + 'education_for_kids' => 'Utbildning för barn', + 'parenting' => 'Föräldraskap', + 'pets_and_animals' => 'Husdjur & djur', + 'stories_for_kids' => 'Berättelser för barn', + 'animation_and_manga' => 'Animering & Manga', + 'automotive' => 'Fordon', + 'aviation' => 'Luftfart', + 'crafts' => 'Hantverk', + 'games' => 'Spel', + 'hobbies' => 'Fritidsintressen', + 'home_and_garden' => 'Hem och trädgård', + 'video_games' => 'Videospel', + 'music_commentary' => 'Kommentar från musik', + 'music_history' => 'Musikhistorik', + 'music_interviews' => 'Musikintervjuer', + 'business_news' => 'Företagsnyheter', + 'daily_news' => 'Dagliga nyheter', + 'entertainment_news' => 'Underhållningsnyheter', + 'news_commentary' => 'Nyheter Kommentar', + 'politics' => 'Politik', + 'sports_news' => 'Sportnyheter', + 'tech_news' => 'Tekniknyheter', + 'buddhism' => 'Budism', + 'christianity' => 'Kristendom', 'hinduism' => 'Hinduism', 'islam' => 'Islam', - 'judaism' => 'Judaism', + 'judaism' => 'Judendom', 'religion' => 'Religion', - 'spirituality' => 'Spirituality', - 'astronomy' => 'Astronomy', - 'chemistry' => 'Chemistry', - 'earth_sciences' => 'Earth Sciences', - 'life_sciences' => 'Life Sciences', - 'mathematics' => 'Mathematics', - 'natural_sciences' => 'Natural Sciences', - 'nature' => 'Nature', - 'physics' => 'Physics', - 'social_sciences' => 'Social Sciences', - 'documentary' => 'Documentary', - 'personal_journals' => 'Personal Journals', - 'philosophy' => 'Philosophy', - 'places_and_travel' => 'Places & Travel', - 'relationships' => 'Relationships', + 'spirituality' => 'Andlighet', + 'astronomy' => 'Astronomi', + 'chemistry' => 'Kemi', + 'earth_sciences' => 'Jordvetenskap', + 'life_sciences' => 'Livsvetenskaper', + 'mathematics' => 'Matematik', + 'natural_sciences' => 'Naturvetenskap', + 'nature' => 'Natur', + 'physics' => 'Fysik', + 'social_sciences' => 'Samhällsvetenskap', + 'documentary' => 'Dokumentär', + 'personal_journals' => 'Personliga tidsskrifter', + 'philosophy' => 'Filosofi', + 'places_and_travel' => 'Resor & Resmål', + 'relationships' => 'Relationer', 'baseball' => 'Baseball', - 'basketball' => 'Basketball', - 'cricket' => 'Cricket', - 'fantasy_sports' => 'Fantasy Sports', - 'football' => 'Football', + 'basketball' => 'Basket', + 'cricket' => 'Kricket', + 'fantasy_sports' => 'Fantasy Sport', + 'football' => 'Fotboll', 'golf' => 'Golf', 'hockey' => 'Hockey', 'rugby' => 'Rugby', - 'running' => 'Running', - 'soccer' => 'Soccer', - 'swimming' => 'Swimming', + 'running' => 'Löpning', + 'soccer' => 'Fotboll', + 'swimming' => 'Simning', 'tennis' => 'Tennis', - 'volleyball' => 'Volleyball', - 'wilderness' => 'Wilderness', - 'wrestling' => 'Wrestling', - 'after_shows' => 'After Shows', - 'film_history' => 'Film History', - 'film_interviews' => 'Film Interviews', - 'film_reviews' => 'Film Reviews', - 'tv_reviews' => 'TV Reviews', + 'volleyball' => 'Volleyboll', + 'wilderness' => 'Vildmark', + 'wrestling' => 'Brottning', + 'after_shows' => 'Efter serier', + 'film_history' => 'Film Historik', + 'film_interviews' => 'Filmintervjuer', + 'film_reviews' => 'Filmrecensioner', + 'tv_reviews' => 'TV recensioner', ], 'publish_form' => [ - 'back_to_podcast_dashboard' => 'Back to podcast dashboard', - 'post' => 'Your announcement post', + 'back_to_podcast_dashboard' => 'Tillbaka till podcast instrumentpanel', + 'post' => 'Ditt meddelande inlägg', 'post_hint' => - "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", - 'message_placeholder' => 'Write your message…', - 'submit' => 'Publish', - 'publication_date' => 'Publication date', + "Skriv ett meddelande för att meddela publiceringen av din podcast. Meddelandet kommer att visas på din podcasts hemsida.", + 'message_placeholder' => 'Skriv ditt meddelande…', + 'submit' => 'Publicera', + 'publication_date' => 'Publiceringsdatum', 'publication_method' => [ - 'now' => 'Now', - 'schedule' => 'Schedule', + 'now' => 'Nu', + 'schedule' => 'Schemalägg', ], - 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date' => 'Planerat publiceringsdatum', 'scheduled_publication_date_hint' => - 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', - 'submit_edit' => 'Edit publication', - 'cancel_publication' => 'Cancel publication', - 'message_warning' => 'You did not write a message for your announcement post!', - 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', - 'message_warning_submit' => 'Publish anyway', + 'Du kan schemalägga podcast-utgåvan genom att ställa in ett framtida publiceringsdatum. Detta fält måste formateras som YYYY-MM-DD HH:mm', + 'submit_edit' => 'Redigera publikation', + 'cancel_publication' => 'Avbryt publicering', + 'message_warning' => 'Du skrev inte ett meddelande för ditt tillkännagivande!', + 'message_warning_hint' => 'Att ha ett meddelande ökar socialt engagemang, vilket resulterar i en bättre synlighet för din podcast.', + 'message_warning_submit' => 'Publicera ändå', ], 'publication_status_banner' => [ - 'draft_mode' => 'draft mode', - 'not_published' => 'This podcast is not yet published.', - 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + 'draft_mode' => 'utkastläge', + 'not_published' => 'Denna podcast är ännu inte publicerad.', + 'scheduled' => 'Denna podcast är schemalagd för publicering på {publication_date}.', ], 'delete_form' => [ 'disclaimer' => - "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", - 'understand' => 'I understand, I want the podcast to be permanently deleted', - 'submit' => 'Delete', + "Ta bort podcasten kommer att ta bort alla avsnitt, mediefiler, inlägg och analytics i samband med det. Denna åtgärd är oåterkallelig, du kommer inte att kunna hämta dem efteråt.", + 'understand' => 'Jag förstår, jag vill att podcasten ska raderas permanent', + 'submit' => 'Radera', ], - 'by' => 'By {publisher}', - 'season' => 'Season {seasonNumber}', - 'list_of_episodes_year' => '{year} episodes ({episodeCount})', + 'by' => 'Av {publisher}', + 'season' => 'Säsong {seasonNumber}', + 'list_of_episodes_year' => '{year} avsnitt ({episodeCount})', 'list_of_episodes_season' => - 'Season {seasonNumber} episodes ({episodeCount})', - 'no_episode' => 'No episode found!', - 'follow' => 'Follow', + 'Säsong {seasonNumber} avsnitt ({episodeCount})', + 'no_episode' => 'Inga avsnitt hittades!', + 'follow' => 'Följ', 'followers' => '{numberOfFollowers, plural, - one {# follower} - other {# followers} + one {# följare} + other {# följare} }', 'posts' => '{numberOfPosts, plural, - one {# post} - other {# posts} + one {# inlägg} + other {# inlägg} }', - 'activity' => 'Activity', - 'episodes' => 'Episodes', + 'activity' => 'Aktivitet', + 'episodes' => 'Avsnitt', 'sponsor' => 'Sponsor', - 'funding_links' => 'Funding links for {podcastTitle}', - 'find_on' => 'Find {podcastTitle} on', - 'listen_on' => 'Listen on', + 'funding_links' => 'Finansierar länkar för {podcastTitle}', + 'find_on' => 'Hitta {podcastTitle} på', + 'listen_on' => 'Lyssna på', ]; diff --git a/modules/Admin/Language/sv/PodcastImport.php b/modules/Admin/Language/sv/PodcastImport.php deleted file mode 100644 index 7c3ef67d..00000000 --- a/modules/Admin/Language/sv/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - 'This procedure may take a long time. As the current version does not show any progress while it runs, you will not see anything updated until it is done. In case of timeout error, increase `max_execution_time` value.', - 'old_podcast_section_title' => 'The podcast to import', - 'old_podcast_section_subtitle' => - 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', - 'imported_feed_url' => 'Feed URL', - 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', - 'new_podcast_section_title' => 'The new podcast', - 'advanced_params_section_title' => 'Advanced parameters', - 'advanced_params_section_subtitle' => - 'Keep the default values if you have no idea of what the fields are for.', - 'slug_field' => 'Field to be used to calculate episode slug', - 'description_field' => - 'Source field used for episode description / show notes', - 'force_renumber' => 'Force episodes renumbering', - 'force_renumber_hint' => - 'Use this if your podcast does not have episode numbers but wish to set them during import.', - 'season_number' => 'Season number', - 'season_number_hint' => - 'Use this if your podcast does not have a season number but wish to set one during import. Leave blank otherwise.', - 'max_episodes' => 'Maximum number of episodes to import', - 'max_episodes_hint' => 'Leave blank to import all episodes', - 'lock_import' => - 'This feed is protected. You cannot import it. If you are the owner, unprotect it on the origin platform.', - 'submit' => 'Import podcast', -]; diff --git a/modules/Admin/Language/sv/PodcastNavigation.php b/modules/Admin/Language/sv/PodcastNavigation.php index b4d7ddc0..383f8817 100644 --- a/modules/Admin/Language/sv/PodcastNavigation.php +++ b/modules/Admin/Language/sv/PodcastNavigation.php @@ -9,30 +9,34 @@ declare(strict_types=1); */ return [ - 'go_to_page' => 'Go to podcast page', - 'dashboard' => 'Podcast dashboard', - 'podcast-view' => 'Home', - 'podcast-edit' => 'Edit podcast', - 'podcast-persons-manage' => 'Manage persons', - 'episodes' => 'Episodes', - 'episode-list' => 'All episodes', - 'episode-create' => 'New episode', - 'analytics' => 'Analytics', - 'podcast-analytics' => 'Audience overview', - 'podcast-analytics-webpages' => 'Web pages visits', - 'podcast-analytics-locations' => 'Locations', - 'podcast-analytics-unique-listeners' => 'Unique listeners', - 'podcast-analytics-players' => 'Players', - 'podcast-analytics-listening-time' => 'Listening time', - 'podcast-analytics-time-periods' => 'Time periods', - 'premium' => 'Premium', - 'subscription-list' => 'All subscriptions', - 'subscription-add' => 'Add subscription', - 'contributors' => 'Contributors', - 'contributor-list' => 'All contributors', - 'contributor-add' => 'Add contributor', - 'platforms' => 'External platforms', - 'platforms-podcasting' => 'Podcasting', - 'platforms-social' => 'Social networks', - 'platforms-funding' => 'Funding', + 'go_to_page' => 'Gå till podcast sida', + 'rss_feed' => 'RSS feed', + 'dashboard' => 'Podcast instrumentpanel', + 'podcast-view' => 'Hem', + 'podcast-edit' => 'Redigera podcast', + 'podcast-persons-manage' => 'Hantera personer', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', + 'episodes' => 'Avsnitt', + 'episode-list' => 'Alla avsnitt', + 'episode-create' => 'Nytt avsnitt', + 'analytics' => 'Statistik', + 'podcast-analytics' => 'Lyssnar översikt', + 'podcast-analytics-webpages' => 'Besök på webbsidor', + 'podcast-analytics-locations' => 'Platser', + 'podcast-analytics-unique-listeners' => 'Unika lyssnare', + 'podcast-analytics-players' => 'Spelare', + 'podcast-analytics-listening-time' => 'Lyssningstid', + 'podcast-analytics-time-periods' => 'Tidsperioder', + 'monetization' => 'Monetization', + 'subscription-list' => 'Alla prenumerationer', + 'subscription-create' => 'Add subscription', + 'contributors' => 'Bidragsgivare', + 'contributor-list' => 'Alla bidragsgivare', + 'contributor-add' => 'Lägg till bidragsgivare', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', + 'platforms-social' => 'Sociala nätverk', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', ]; diff --git a/modules/Admin/Language/sv/Settings.php b/modules/Admin/Language/sv/Settings.php index 4a70dcba..6a595e0a 100644 --- a/modules/Admin/Language/sv/Settings.php +++ b/modules/Admin/Language/sv/Settings.php @@ -9,50 +9,50 @@ declare(strict_types=1); */ return [ - 'title' => 'General settings', + 'title' => 'Allmänna inställningar', 'instance' => [ - 'title' => 'Instance', - 'site_icon' => 'Site icon', - 'site_icon_delete' => 'Delete site icon', - 'site_icon_hint' => 'Site icons are what you see on your browser tabs, bookmarks bar, and when you add a website as a shortcut on mobile devices.', - 'site_icon_helper' => 'Icon must be squared and at least 512px wide and tall.', - 'site_name' => 'Site name', - 'site_description' => 'Site description', - 'submit' => 'Save', - 'editSuccess' => 'Instance has been updated successfully!', - 'deleteIconSuccess' => 'Site icon has been remove successfully!', + 'title' => 'Instans', + 'site_icon' => 'Ikon för webbplats', + 'site_icon_delete' => 'Ta bort webbplatsikonen', + 'site_icon_hint' => 'Webbplatsikoner är vad du ser på dina webbläsarflikar, bokmärkesfältet och när du lägger till en webbplats som en genväg på mobila enheter.', + 'site_icon_helper' => 'Ikonen måste vara kvadratisk och minst 512px bred och hög.', + 'site_name' => 'Webbplatsens namn', + 'site_description' => 'Webbplatsbeskrivning', + 'submit' => 'Spara', + 'editSuccess' => 'Instansen har uppdaterats!', + 'deleteIconSuccess' => 'Webbplatsikonen har tagits bort!', ], 'images' => [ - 'title' => 'Images', - 'subtitle' => 'Here you can regenerate all images based on the originals that were uploaded. To be used if you find that some images are missing. This task may take a while.', - 'regenerate' => 'Regenerate images', - 'regenerationSuccess' => 'All images have been regenerated successfully!', + 'title' => 'Bilder', + 'subtitle' => 'Här kan du regenerera alla bilder baserat på originalen som laddats upp. Att användas om du upptäcker att vissa bilder saknas. Denna uppgift kan ta ett tag.', + 'regenerate' => 'Regenerera bilder', + 'regenerationSuccess' => 'Alla bilder har återskapats framgångsrikt!', ], 'housekeeping' => [ - 'title' => 'Housekeeping', - 'subtitle' => 'Runs various housekeeping tasks. Use this feature if you ever encounter issues with media files or data integrity. These tasks may take a while.', - 'reset_counts' => 'Reset counts', - 'reset_counts_helper' => 'This option will recalculate and reset all data counts (number of followers, posts, comments, …).', - 'rewrite_media' => 'Rewrite media metadata', - 'rewrite_media_helper' => 'This option will delete all superfluous media files and recreate them (images, audio files, transcripts, chapters, …)', - 'rename_episodes_files' => 'Rename episode audio files', - 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', - 'clear_cache' => 'Clear all cache', - 'clear_cache_helper' => 'This option will flush redis cache or writable/cache files.', - 'run' => 'Run housekeeping', - 'runSuccess' => 'Housekeeping has been run successfully!', + 'title' => 'Städning', + 'subtitle' => 'Kör olika städuppgifter. Använd denna funktion om du någonsin stöter på problem med mediefiler eller dataintegritet. Dessa uppgifter kan ta ett tag.', + 'reset_counts' => 'Återställ räknare', + 'reset_counts_helper' => 'Detta alternativ kommer att räkna om och återställa alla data räknas (antal följare, inlägg, kommentarer, …).', + 'rewrite_media' => 'Skriv om media metadata', + 'rewrite_media_helper' => 'Detta alternativ kommer att ta bort alla överflödiga mediefiler och återskapa dem (bilder, ljudfiler, avskrifter, kapitel, …)', + 'rename_episodes_files' => 'Döp om avsnittets ljudfiler', + 'rename_episodes_files_hint' => 'Detta alternativ kommer att byta namn på alla avsnitt ljudfiler till en slumpmässig sträng av tecken. Använd detta om en av dina privata episoder länk läckte eftersom detta effektivt kommer att dölja det.', + 'clear_cache' => 'Rensa all cache', + 'clear_cache_helper' => 'Det här alternativet kommer att radera redis cache eller skrivbara/cache-filer.', + 'run' => 'Kör städning', + 'runSuccess' => 'Städning har körts framgångsrikt!', ], 'theme' => [ - 'title' => 'Theme', - 'accent_section_title' => 'Accent color', - 'accent_section_subtitle' => 'Choose the color to determine the look and feel of all public pages.', - 'pine' => 'Pine', - 'crimson' => 'Crimson', - 'amber' => 'Amber', - 'lake' => 'Lake', + 'title' => 'Tema', + 'accent_section_title' => 'Accentfärg', + 'accent_section_subtitle' => 'Välj färg för att bestämma utseendet och känslan på alla offentliga sidor.', + 'pine' => 'Tall', + 'crimson' => 'Karmosinröd', + 'amber' => 'Bärnsten', + 'lake' => 'Sjö', 'jacaranda' => 'Jacaranda', 'onyx' => 'Onyx', - 'submit' => 'Save', - 'setInstanceThemeSuccess' => 'Theme has been updated successfully!', + 'submit' => 'Spara', + 'setInstanceThemeSuccess' => 'Temat har uppdaterats!', ], ]; diff --git a/modules/Admin/Language/sv/Soundbite.php b/modules/Admin/Language/sv/Soundbite.php index a3f828fe..fd80f828 100644 --- a/modules/Admin/Language/sv/Soundbite.php +++ b/modules/Admin/Language/sv/Soundbite.php @@ -10,22 +10,22 @@ declare(strict_types=1); return [ 'list' => [ - 'title' => 'Soundbites', - 'soundbite' => 'Soundbite', + 'title' => 'Ljudklipp', + 'soundbite' => 'Ljudklipp', ], 'messages' => [ - 'createSuccess' => 'Soundbite has been successfully created!', - 'deleteSuccess' => 'Soundbite has been successfully removed!', + 'createSuccess' => 'Ljudklipp har skapats!', + 'deleteSuccess' => 'Ljudklipp har tagits bort!', ], 'form' => [ - 'title' => 'New soundbite', - 'soundbite_title' => 'Soundbite title', - 'start_time' => 'Start at', - 'duration' => 'Duration', - 'submit' => 'Create soundbite', + 'title' => 'Nytt ljudklipp', + 'soundbite_title' => 'Ljudklipp titel', + 'start_time' => 'Starta vid', + 'duration' => 'Längd', + 'submit' => 'Skapa ljudklipp', ], - 'play' => 'Play soundbite', - 'stop' => 'Stop soundbite', - 'create' => 'New soundbite', - 'delete' => 'Delete soundbite', + 'play' => 'Spela ljudklipp', + 'stop' => 'Stoppa ljudklipp', + 'create' => 'Nytt ljudklipp', + 'delete' => 'Ta bort ljudklipp', ]; diff --git a/modules/Admin/Language/sv/User.php b/modules/Admin/Language/sv/User.php deleted file mode 100644 index 585d6799..00000000 --- a/modules/Admin/Language/sv/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', - 'ban' => 'Ban', - 'unban' => 'Unban', - 'delete' => 'Delete', - 'create' => 'New user', - 'view' => "{username}'s info", - 'all_users' => 'All users', - 'list' => [ - 'user' => 'User', - 'roles' => 'Roles', - 'banned' => 'Banned?', - ], - 'form' => [ - 'email' => 'Email', - 'username' => 'Username', - 'password' => 'Password', - 'new_password' => 'New Password', - 'roles' => 'Roles', - 'permissions' => 'Permissions', - 'submit_create' => 'Create user', - 'submit_edit' => 'Save', - 'submit_password_change' => 'Change!', - ], - 'roles' => [ - 'superadmin' => 'Super admin', - ], - 'messages' => [ - 'createSuccess' => - 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => - "{username}'s roles have been successfully updated.", - 'forcePassResetSuccess' => - '{username} will be prompted with a password reset upon next visit.', - 'banSuccess' => '{username} has been banned.', - 'unbanSuccess' => '{username} has been unbanned.', - 'editOwnerError' => - '{username} is the instance owner, you cannot edit its roles.', - 'banSuperAdminError' => - '{username} is a superadmin, one does not simply ban a superadmin…', - 'deleteSuperAdminError' => - '{username} is a superadmin, one does not simply delete a superadmin…', - 'deleteSuccess' => '{username} has been deleted.', - ], -]; diff --git a/modules/Admin/Language/sv/Validation.php b/modules/Admin/Language/sv/Validation.php index 750b1968..8dc9341e 100644 --- a/modules/Admin/Language/sv/Validation.php +++ b/modules/Admin/Language/sv/Validation.php @@ -10,9 +10,8 @@ declare(strict_types=1); return [ 'min_dims' => - '{field} is either not an image, or it is not wide or tall enough.', + '{field} är antingen inte en bild, eller så är den inte bred eller tillräckligt hög.', 'is_image_ratio' => - '{field} is either not an image or not of the right ratio.', - 'validate_url' => - 'The {field} field must be a valid URL (eg. https://example.com/).', + '{field} är antingen inte en bild eller inte av rätt förhållande.', + 'is_json' => '{field} contains invalid JSON.', ]; diff --git a/modules/Admin/Language/sv/VideoClip.php b/modules/Admin/Language/sv/VideoClip.php index 638de697..a94406b9 100644 --- a/modules/Admin/Language/sv/VideoClip.php +++ b/modules/Admin/Language/sv/VideoClip.php @@ -10,63 +10,63 @@ declare(strict_types=1); return [ 'list' => [ - 'title' => 'Video clips', + 'title' => 'Videoklipp', 'status' => [ 'label' => 'Status', - 'queued' => 'queued', - 'queued_hint' => 'Clip is waiting to be processed.', - 'pending' => 'pending', - 'pending_hint' => 'Clip will be generated shortly.', - 'running' => 'running', - 'running_hint' => 'Clip is being generated.', - 'failed' => 'failed', - 'failed_hint' => 'Clip could not be generated: script failure.', - 'passed' => 'passed', - 'passed_hint' => 'Clip was generated successfully!', + 'queued' => 'köad', + 'queued_hint' => 'Klipp väntar på att bearbetas.', + 'pending' => 'väntande', + 'pending_hint' => 'Klipp kommer att genereras inom kort.', + 'running' => 'körs', + 'running_hint' => 'Klipp genereras.', + 'failed' => 'misslyckades', + 'failed_hint' => 'Klipp kunde inte genereras: skript misslyckades.', + 'passed' => 'godkänd', + 'passed_hint' => 'Klipp har skapats framgångsrikt!', ], - 'clip' => 'Clip', - 'duration' => 'Job duration', + 'clip' => 'Klipp', + 'duration' => 'Varaktighet för jobb', ], - 'title' => 'Video clip: {videoClipLabel}', - 'download_clip' => 'Download clip', - 'create' => 'New video clip', - 'go_to_page' => 'Go to clip page', - 'retry' => 'Retry clip generation', - 'delete' => 'Delete clip', + 'title' => 'Videoklipp: {videoClipLabel}', + 'download_clip' => 'Ladda ner klipp', + 'create' => 'Nytt videoklipp', + 'go_to_page' => 'Gå till klippsida', + 'retry' => 'Generering av nytt klipp', + 'delete' => 'Ta bort klipp', 'logs' => 'Job logs', 'messages' => [ - 'alreadyExistingError' => 'The video clip you are trying to create already exists!', - 'addToQueueSuccess' => 'Video clip has been added to queue, awaiting to be created!', - 'deleteSuccess' => 'Video clip has been successfully removed!', + 'alreadyExistingError' => 'Det videoklipp du försöker skapa finns redan!', + 'addToQueueSuccess' => 'Videoklipp har lagts till i kön, väntar på att skapas!', + 'deleteSuccess' => 'Videoklipp har tagits bort!', ], 'format' => [ - 'landscape' => 'Landscape', - 'portrait' => 'Portrait', - 'squared' => 'Squared', + 'landscape' => 'Liggande', + 'portrait' => 'Stående', + 'squared' => 'Kvadrat', ], 'form' => [ - 'title' => 'New video clip', - 'params_section_title' => 'Video clip parameters', - 'clip_title' => 'Clip title', + 'title' => 'Nytt videoklipp', + 'params_section_title' => 'Parametrar för videoklipp', + 'clip_title' => 'Klipp titel', 'format' => [ - 'label' => 'Choose a format', - 'landscape_hint' => 'With a 16:9 ratio, landscape videos are great for PeerTube, Youtube and Vimeo.', - 'portrait_hint' => 'With a 9:16 ratio, portrait videos are great for TikTok, Youtube shorts and Instagram stories.', - 'squared_hint' => 'With a 1:1 ratio, squared videos are great for Mastodon, Facebook, Twitter and LinkedIn.', + 'label' => 'Välj ett format', + 'landscape_hint' => 'Med ett 16:9 förhållande, landskapsvideor är bra för PeerTube, Youtube och Vimeo.', + 'portrait_hint' => 'Med 9:16 förhållande, porträttfilmer är bra för TikTok, Youtube shorts och Instagram berättelser.', + 'squared_hint' => 'Med en 1:1 förhållande, fyrkantiga videor är bra för Mastodon, Facebook, Twitter och LinkedIn.', ], - 'theme' => 'Select a theme', - 'start_time' => 'Start at', - 'duration' => 'Duration', - 'trim_start' => 'Trim start', - 'trim_end' => 'Trim end', - 'submit' => 'Create video clip', + 'theme' => 'Välj ett tema', + 'start_time' => 'Starta vid', + 'duration' => 'Längd', + 'trim_start' => 'Trimma start', + 'trim_end' => 'Trimma slut', + 'submit' => 'Skapa videoklipp', ], 'requirements' => [ - 'title' => 'Missing requirements', - 'missing' => 'You have missing requirements. Make sure to add all the required items to be allowed creating a video for this episode!', + 'title' => 'Krav ej uppfyllda', + 'missing' => 'Du har saknade krav. Se till att lägga till alla nödvändiga objekt som tillåts att skapa en video för detta avsnitt!', 'ffmpeg' => 'FFmpeg', - 'gd' => 'Graphics Draw (GD)', - 'freetype' => 'Freetype library for GD', - 'transcript' => 'Transcript file (.srt)', + 'gd' => 'Grafik Rita (GD)', + 'freetype' => 'Freetyp bibliotek för GD', + 'transcript' => 'Avskrift fil (.srt)', ], ]; diff --git a/modules/Admin/Language/uk/AboutCastopod.php b/modules/Admin/Language/uk/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/uk/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/uk/Breadcrumb.php b/modules/Admin/Language/uk/Breadcrumb.php new file mode 100644 index 00000000..408c9f9f --- /dev/null +++ b/modules/Admin/Language/uk/Breadcrumb.php @@ -0,0 +1,57 @@ + 'breadcrumb', + config('Admin') + ->gateway => 'Home', + 'podcasts' => 'podcasts', + 'episodes' => 'episodes', + 'subscriptions' => 'subscriptions', + 'contributors' => 'contributors', + 'pages' => 'pages', + 'settings' => 'settings', + 'theme' => 'theme', + 'about' => 'about', + 'add' => 'add', + 'new' => 'new', + 'edit' => 'edit', + 'persons' => 'persons', + 'publish' => 'publish', + 'publish-edit' => 'edit publication', + 'publish-date-edit' => 'edit publication date', + 'unpublish' => 'unpublish', + 'delete' => 'delete', + 'remove' => 'remove', + 'fediverse' => 'fediverse', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', + 'users' => 'users', + 'my-account' => 'my account', + 'change-password' => 'change password', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', + 'platforms' => 'platforms', + 'social' => 'social networks', + 'funding' => 'funding', + 'monetization-other' => 'other monetization', + 'analytics' => 'analytics', + 'locations' => 'locations', + 'webpages' => 'web pages', + 'unique-listeners' => 'unique listeners', + 'players' => 'players', + 'listening-time' => 'listening time', + 'time-periods' => 'time periods', + 'soundbites' => 'soundbites', + 'video-clips' => 'video clips', + 'embed' => 'embeddable player', + 'notifications' => 'notifications', + 'suspend' => 'suspend', +]; diff --git a/modules/Admin/Language/uk/Charts.php b/modules/Admin/Language/uk/Charts.php new file mode 100644 index 00000000..6ede2510 --- /dev/null +++ b/modules/Admin/Language/uk/Charts.php @@ -0,0 +1,41 @@ + 'Episode downloads by service (for the past week)', + 'by_player_weekly' => 'Episode downloads by player (for the past week)', + 'by_player_yearly' => 'Episode downloads by player (for the past year)', + 'by_device_weekly' => 'Episode downloads by device (for the past week)', + 'by_os_weekly' => 'Episode downloads by O.S. (for the past week)', + 'podcast_by_region' => 'Episode downloads by region (for the past week)', + 'unique_daily_listeners' => 'Daily unique listeners', + 'unique_monthly_listeners' => 'Monthly unique listeners', + 'by_browser' => 'Web pages usage by browser (for the past week)', + 'podcast_by_day' => 'Episode daily downloads', + 'podcast_by_month' => 'Episode monthly downloads', + 'episode_by_day' => 'Episode daily downloads (first 60 days)', + 'episode_by_month' => 'Episode monthly downloads', + 'episodes_by_day' => + '5 latest episodes downloads (during their first 60 days)', + 'by_country_weekly' => 'Episode downloads by country (for the past week)', + 'by_country_yearly' => 'Episode downloads by country (for the past year)', + 'by_domain_weekly' => 'Web pages visits by source (for the past week)', + 'by_domain_yearly' => 'Web pages visits by source (for the past year)', + 'by_entry_page' => 'Web pages visits by landing page (for the past week)', + 'podcast_bots' => 'Bots (crawlers)', + 'daily_listening_time' => 'Daily cumulative listening time', + 'monthly_listening_time' => 'Monthly cumulative listening time', + 'by_weekday' => 'By week day (for the past 60 days)', + 'by_hour' => 'By time of day (for the past 60 days)', + 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', + 'total_storage_by_month' => 'Monthly storage (in MB)', + 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', +]; diff --git a/modules/Admin/Language/uk/Common.php b/modules/Admin/Language/uk/Common.php new file mode 100644 index 00000000..74addcf2 --- /dev/null +++ b/modules/Admin/Language/uk/Common.php @@ -0,0 +1,52 @@ + 'Yes', + 'no' => 'No', + 'cancel' => 'Cancel', + 'optional' => 'Optional', + 'more' => 'More', + 'no_data' => 'No data found!', + 'close' => 'Close', + 'edit' => 'Edit', + 'copy' => 'Copy', + 'copied' => 'Copied!', + 'home' => 'Home', + 'explicit' => 'Explicit', + 'powered_by' => 'Powered by {castopod}', + 'actions' => 'Actions', + 'pageInfo' => 'Page {currentPage} out of {pageCount}', + 'go_back' => 'Go back', + 'forms' => [ + 'editor' => [ + 'write' => 'Write', + 'preview' => 'Preview', + 'help' => 'Powered by markdown', + ], + 'multiSelect' => [ + 'selectText' => 'Press to select', + 'loadingText' => 'Loading…', + 'noResultsText' => 'No results found', + 'noChoicesText' => 'No choices to choose from', + 'maxItemText' => 'Cannot add more items', + ], + 'upload_file' => 'Upload a file', + 'remote_url' => 'Remote URL', + 'save' => 'Save', + ], + 'play_episode_button' => [ + 'play' => 'Play', + 'playing' => 'Playing', + ], + 'size_limit' => 'Size limit: {0}.', + 'choose_interact' => 'Choose how to interact', + 'view' => 'View', +]; diff --git a/modules/Admin/Language/uk/Countries.php b/modules/Admin/Language/uk/Countries.php new file mode 100644 index 00000000..4cd5d9c8 --- /dev/null +++ b/modules/Admin/Language/uk/Countries.php @@ -0,0 +1,264 @@ + 'Andorra', + 'AE' => 'United Arab Emirates', + 'AF' => 'Afghanistan', + 'AG' => 'Antigua and Barbuda', + 'AI' => 'Anguilla', + 'AL' => 'Albania', + 'AM' => 'Armenia', + 'AO' => 'Angola', + 'AQ' => 'Antarctica', + 'AR' => 'Argentina', + 'AS' => 'American Samoa', + 'AT' => 'Austria', + 'AU' => 'Australia', + 'AW' => 'Aruba', + 'AX' => 'Åland Islands', + 'AZ' => 'Azerbaijan', + 'BA' => 'Bosnia and Herzegovina', + 'BB' => 'Barbados', + 'BD' => 'Bangladesh', + 'BE' => 'Belgium', + 'BF' => 'Burkina Faso', + 'BG' => 'Bulgaria', + 'BH' => 'Bahrain', + 'BI' => 'Burundi', + 'BJ' => 'Benin', + 'BL' => 'Saint Barthélemy', + 'BM' => 'Bermuda', + 'BN' => 'Brunei Darussalam', + 'BO' => 'Bolivia, Plurinational State of', + 'BQ' => 'Bonaire, Sint Eustatius and Saba', + 'BR' => 'Brazil', + 'BS' => 'Bahamas', + 'BT' => 'Bhutan', + 'BV' => 'Bouvet Island', + 'BW' => 'Botswana', + 'BY' => 'Belarus', + 'BZ' => 'Belize', + 'CA' => 'Canada', + 'CC' => 'Cocos (Keeling) Islands', + 'CD' => 'Congo, the Democratic Republic of the', + 'CF' => 'Central African Republic', + 'CG' => 'Congo', + 'CH' => 'Switzerland', + 'CI' => "Côte d'Ivoire", + 'CK' => 'Cook Islands', + 'CL' => 'Chile', + 'CM' => 'Cameroon', + 'CN' => 'China', + 'CO' => 'Colombia', + 'CR' => 'Costa Rica', + 'CU' => 'Cuba', + 'CV' => 'Cape Verde', + 'CW' => 'Curaçao', + 'CX' => 'Christmas Island', + 'CY' => 'Cyprus', + 'CZ' => 'Czech Republic', + 'DE' => 'Germany', + 'DJ' => 'Djibouti', + 'DK' => 'Denmark', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'DZ' => 'Algeria', + 'EC' => 'Ecuador', + 'EE' => 'Estonia', + 'EG' => 'Egypt', + 'EH' => 'Western Sahara', + 'ER' => 'Eritrea', + 'ES' => 'Spain', + 'ET' => 'Ethiopia', + 'FI' => 'Finland', + 'FJ' => 'Fiji', + 'FK' => 'Falkland Islands (Malvinas)', + 'FM' => 'Micronesia, Federated States of', + 'FO' => 'Faroe Islands', + 'FR' => 'France', + 'GA' => 'Gabon', + 'GB' => 'United Kingdom', + 'GD' => 'Grenada', + 'GE' => 'Georgia', + 'GF' => 'French Guiana', + 'GG' => 'Guernsey', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GL' => 'Greenland', + 'GM' => 'Gambia', + 'GN' => 'Guinea', + 'GP' => 'Guadeloupe', + 'GQ' => 'Equatorial Guinea', + 'GR' => 'Greece', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'GT' => 'Guatemala', + 'GU' => 'Guam', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HK' => 'Hong Kong', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HR' => 'Croatia', + 'HT' => 'Haiti', + 'HU' => 'Hungary', + 'ID' => 'Indonesia', + 'IE' => 'Ireland', + 'IL' => 'Israel', + 'IM' => 'Isle of Man', + 'IN' => 'India', + 'IO' => 'British Indian Ocean Territory', + 'IQ' => 'Iraq', + 'IR' => 'Iran, Islamic Republic of', + 'IS' => 'Iceland', + 'IT' => 'Italy', + 'JE' => 'Jersey', + 'JM' => 'Jamaica', + 'JO' => 'Jordan', + 'JP' => 'Japan', + 'KE' => 'Kenya', + 'KG' => 'Kyrgyzstan', + 'KH' => 'Cambodia', + 'KI' => 'Kiribati', + 'KM' => 'Comoros', + 'KN' => 'Saint Kitts and Nevis', + 'KP' => "Korea, Democratic People's Republic of", + 'KR' => 'Korea, Republic of', + 'KW' => 'Kuwait', + 'KY' => 'Cayman Islands', + 'KZ' => 'Kazakhstan', + 'LA' => "Lao People's Democratic Republic", + 'LB' => 'Lebanon', + 'LC' => 'Saint Lucia', + 'LI' => 'Liechtenstein', + 'LK' => 'Sri Lanka', + 'LR' => 'Liberia', + 'LS' => 'Lesotho', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'LV' => 'Latvia', + 'LY' => 'Libya', + 'MA' => 'Morocco', + 'MC' => 'Monaco', + 'MD' => 'Moldova, Republic of', + 'ME' => 'Montenegro', + 'MF' => 'Saint Martin (French part)', + 'MG' => 'Madagascar', + 'MH' => 'Marshall Islands', + 'MK' => 'Macedonia, the Former Yugoslav Republic of', + 'ML' => 'Mali', + 'MM' => 'Myanmar', + 'MN' => 'Mongolia', + 'MO' => 'Macao', + 'MP' => 'Northern Mariana Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MS' => 'Montserrat', + 'MT' => 'Malta', + 'MU' => 'Mauritius', + 'MV' => 'Maldives', + 'MW' => 'Malawi', + 'MX' => 'Mexico', + 'MY' => 'Malaysia', + 'MZ' => 'Mozambique', + 'N/A' => 'Not Applicable (local IP…)', + 'NA' => 'Namibia', + 'NC' => 'New Caledonia', + 'NE' => 'Niger', + 'NF' => 'Norfolk Island', + 'NG' => 'Nigeria', + 'NI' => 'Nicaragua', + 'NL' => 'Netherlands', + 'NO' => 'Norway', + 'NP' => 'Nepal', + 'NR' => 'Nauru', + 'NU' => 'Niue', + 'NZ' => 'New Zealand', + 'OM' => 'Oman', + 'PA' => 'Panama', + 'PE' => 'Peru', + 'PF' => 'French Polynesia', + 'PG' => 'Papua New Guinea', + 'PH' => 'Philippines', + 'PK' => 'Pakistan', + 'PL' => 'Poland', + 'PM' => 'Saint Pierre and Miquelon', + 'PN' => 'Pitcairn', + 'PR' => 'Puerto Rico', + 'PS' => 'Palestine, State of', + 'PT' => 'Portugal', + 'PW' => 'Palau', + 'PY' => 'Paraguay', + 'QA' => 'Qatar', + 'RE' => 'Réunion', + 'RO' => 'Romania', + 'RS' => 'Serbia', + 'RU' => 'Russian Federation', + 'RW' => 'Rwanda', + 'SA' => 'Saudi Arabia', + 'SB' => 'Solomon Islands', + 'SC' => 'Seychelles', + 'SD' => 'Sudan', + 'SE' => 'Sweden', + 'SG' => 'Singapore', + 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', + 'SI' => 'Slovenia', + 'SJ' => 'Svalbard and Jan Mayen', + 'SK' => 'Slovakia', + 'SL' => 'Sierra Leone', + 'SM' => 'San Marino', + 'SN' => 'Senegal', + 'SO' => 'Somalia', + 'SR' => 'Suriname', + 'SS' => 'South Sudan', + 'ST' => 'Sao Tome and Principe', + 'SV' => 'El Salvador', + 'SX' => 'Sint Maarten (Dutch part)', + 'SY' => 'Syrian Arab Republic', + 'SZ' => 'Swaziland', + 'TC' => 'Turks and Caicos Islands', + 'TD' => 'Chad', + 'TF' => 'French Southern Territories', + 'TG' => 'Togo', + 'TH' => 'Thailand', + 'TJ' => 'Tajikistan', + 'TK' => 'Tokelau', + 'TL' => 'Timor-Leste', + 'TM' => 'Turkmenistan', + 'TN' => 'Tunisia', + 'TO' => 'Tonga', + 'TR' => 'Turkey', + 'TT' => 'Trinidad and Tobago', + 'TV' => 'Tuvalu', + 'TW' => 'Taiwan, Province of China', + 'TZ' => 'Tanzania, United Republic of', + 'UA' => 'Ukraine', + 'UG' => 'Uganda', + 'UM' => 'United States Minor Outlying Islands', + 'US' => 'United States', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VA' => 'Holy See (Vatican City State)', + 'VC' => 'Saint Vincent and the Grenadines', + 'VE' => 'Venezuela, Bolivarian Republic of', + 'VG' => 'Virgin Islands, British', + 'VI' => 'Virgin Islands, U.S.', + 'VN' => 'Viet Nam', + 'VU' => 'Vanuatu', + 'WF' => 'Wallis and Futuna', + 'WS' => 'Samoa', + 'YE' => 'Yemen', + 'YT' => 'Mayotte', + 'ZA' => 'South Africa', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', +]; diff --git a/modules/Admin/Language/uk/Dashboard.php b/modules/Admin/Language/uk/Dashboard.php new file mode 100644 index 00000000..881073fd --- /dev/null +++ b/modules/Admin/Language/uk/Dashboard.php @@ -0,0 +1,28 @@ + 'Admin dashboard', + 'welcome_message' => 'Welcome to the admin area!', + 'podcasts' => [ + 'title' => 'Podcasts', + 'not_found' => 'No published podcast', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'episodes' => [ + 'title' => 'Episodes', + 'not_found' => 'No published episode', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'storage' => [ + 'title' => 'Storage', + 'subtitle' => '{totalUploaded} out of {totalStorage}', + ], +]; diff --git a/modules/Admin/Language/uk/Episode.php b/modules/Admin/Language/uk/Episode.php new file mode 100644 index 00000000..4fa846e3 --- /dev/null +++ b/modules/Admin/Language/uk/Episode.php @@ -0,0 +1,225 @@ + 'Season {seasonNumber}', + 'season_abbr' => 'S{seasonNumber}', + 'number' => 'Episode {episodeNumber}', + 'number_abbr' => 'Ep. {episodeNumber}', + 'season_episode' => 'Season {seasonNumber} episode {episodeNumber}', + 'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}', + 'number_of_comments' => '{numberOfComments, plural, + one {# comment} + other {# comments} + }', + 'all_podcast_episodes' => 'All podcast episodes', + 'back_to_podcast' => 'Go back to podcast', + 'edit' => 'Edit', + 'preview' => 'Preview', + 'publish' => 'Publish', + 'publish_edit' => 'Edit publication', + 'publish_date_edit' => 'Edit publication date', + 'unpublish' => 'Unpublish', + 'publish_error' => 'Episode is already published.', + 'publish_edit_error' => 'Episode is already published.', + 'publish_cancel_error' => 'Episode is already published.', + 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', + 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', + 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'unpublish_error' => 'Episode is not published.', + 'delete' => 'Delete', + 'go_to_page' => 'Go to page', + 'create' => 'Add an episode', + 'publication_status' => [ + 'published' => 'Published', + 'with_podcast' => 'Published', + 'scheduled' => 'Scheduled', + 'not_published' => 'Not published', + ], + 'with_podcast_hint' => 'To be published at the same time as the podcast', + 'list' => [ + 'search' => [ + 'placeholder' => 'Search for an episode', + 'clear' => 'Clear search', + 'submit' => 'Search', + ], + 'number_of_episodes' => '{numberOfEpisodes, plural, + one {# episode} + other {# episodes} + }', + 'episode' => 'Episode', + 'visibility' => 'Visibility', + 'downloads' => 'Downloads', + 'comments' => 'Comments', + 'actions' => 'Actions', + ], + 'messages' => [ + 'createSuccess' => 'Episode has been successfully created!', + 'editSuccess' => 'Episode has been successfully updated!', + 'publishSuccess' => '{publication_status, select, + published {Episode successfully published!} + scheduled {Episode publication successfully scheduled!} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not published.} + }', + 'publishCancelSuccess' => 'Episode publication successfully cancelled!', + 'unpublishBeforeDeleteTip' => 'You must unpublish the episode before deleting it.', + 'scheduleDateError' => 'Schedule date must be set!', + 'deletePublishedEpisodeError' => 'Please unpublish the episode before deleting it.', + 'deleteSuccess' => 'Episode successfully deleted!', + 'deleteError' => 'Failed to delete episode {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deleteFileError' => 'Failed to delete {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + } file {file_key}. You may manually remove it from your disk.', + 'sameSlugError' => 'An episode with the chosen slug already exists.', + ], + 'form' => [ + 'file_size_error' => + 'Your file size is too big! Max size is {0}. Increase the `memory_limit`, `upload_max_filesize` and `post_max_size` values in your php configuration file then restart your web server to upload your file.', + 'audio_file' => 'Audio file', + 'audio_file_hint' => 'Choose an .mp3 or .m4a audio file.', + 'info_section_title' => 'Episode info', + 'cover' => 'Episode cover', + 'cover_hint' => + 'If you do not set a cover, the podcast cover will be used instead.', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'title' => 'Title', + 'title_hint' => + 'Should contain a clear and concise episode name. Do not specify the episode or season numbers here.', + 'permalink' => 'Permalink', + 'season_number' => 'Season', + 'episode_number' => 'Episode', + 'type' => [ + 'label' => 'Type', + 'full' => 'Full', + 'full_hint' => 'Complete content (the episode)', + 'trailer' => 'Trailer', + 'trailer_hint' => 'Short, promotional piece of content that represents a preview of the current show', + 'bonus' => 'Bonus', + 'bonus_hint' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show', + ], + 'premium_title' => 'Premium', + 'premium' => 'Episode must be accessible to premium subscribers only', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does the episode contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'show_notes_section_title' => 'Show notes', + 'show_notes_section_subtitle' => + 'Up to 4000 characters, be clear and concise. Show notes help potential listeners in finding the episode.', + 'description' => 'Description', + 'description_footer' => 'Description footer', + 'description_footer_hint' => + 'This text is added at the end of each episode description, it is a good place to input your social links for example.', + 'additional_files_section_title' => 'Additional files', + 'additional_files_section_subtitle' => + 'These files may be used by other platforms to provide better experience to your audience. See the {podcastNamespaceLink} for more information.', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this episode about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real or fictional location', + 'transcript' => 'Transcript (subtitles / closed captions)', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', + 'transcript_download' => 'Download transcript', + 'transcript_file' => 'Transcript file (.srt or .vtt)', + 'transcript_remote_url' => 'Remote url for transcript', + 'transcript_file_delete' => 'Delete transcript file', + 'chapters' => 'Chapters', + 'chapters_hint' => 'File must be in JSON Chapters format.', + 'chapters_download' => 'Download chapters', + 'chapters_file' => 'Chapters file', + 'chapters_remote_url' => 'Remote url for chapters file', + 'chapters_file_delete' => 'Delete chapters file', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the episode', + 'custom_rss_hint' => 'This will be injected within the ❬item❭ tag.', + 'block' => 'Episode should be hidden from public catalogues', + 'block_hint' => + 'The episode show or hide status: toggling this on prevents the episode from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'submit_create' => 'Create episode', + 'submit_edit' => 'Save episode', + ], + 'publish_form' => [ + 'back_to_episode_dashboard' => 'Back to episode dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your episode. The message will be broadcasted to all your followers in the fediverse and be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + 'with_podcast' => 'Publish alongside podcast', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_clear' => 'Clear publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the episode release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit' => 'Publish', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your episode.', + 'message_warning_submit' => 'Publish anyways', + ], + 'publish_date_edit_form' => [ + 'new_publication_date' => 'New publication date', + 'new_publication_date_hint' => 'Must be set to a past date.', + 'submit' => 'Edit publication date', + ], + 'unpublish_form' => [ + 'disclaimer' => + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", + 'understand' => 'I understand, I want to unpublish the episode', + 'submit' => 'Unpublish', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.", + 'understand' => 'I understand, I want to delete the episode', + 'submit' => 'Delete', + ], + 'embed' => [ + 'title' => 'Embeddable player', + 'label' => + 'Pick a theme color, copy the embeddable player to clipboard, then paste it on your website.', + 'clipboard_iframe' => 'Copy embeddable player to clipboard', + 'clipboard_url' => 'Copy address to clipboard', + 'dark' => 'Dark', + 'dark-transparent' => 'Dark transparent', + 'light' => 'Light', + 'light-transparent' => 'Light transparent', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], +]; diff --git a/modules/Admin/Language/uk/EpisodeNavigation.php b/modules/Admin/Language/uk/EpisodeNavigation.php new file mode 100644 index 00000000..1406e301 --- /dev/null +++ b/modules/Admin/Language/uk/EpisodeNavigation.php @@ -0,0 +1,23 @@ + 'View episode page', + 'dashboard' => 'Episode dashboard', + 'episode-view' => 'Home', + 'episode-edit' => 'Edit episode', + 'episode-persons-manage' => 'Manage persons', + 'embed-add' => 'Embeddable player', + 'clips' => 'Clips', + 'video-clips-list' => 'Video clips', + 'video-clips-create' => 'New video clip', + 'soundbites-list' => 'Soundbites', + 'soundbites-create' => 'New soundbite', +]; diff --git a/modules/Admin/Language/uk/Fediverse.php b/modules/Admin/Language/uk/Fediverse.php new file mode 100644 index 00000000..0e4ca66d --- /dev/null +++ b/modules/Admin/Language/uk/Fediverse.php @@ -0,0 +1,32 @@ + [ + 'actorNotFound' => 'The account could not be found!', + 'blockActorSuccess' => '{actor} has been blocked!', + 'unblockActorSuccess' => 'Actor has been unblocked!', + 'blockDomainSuccess' => '{domain} has been blocked!', + 'unblockDomainSuccess' => '{domain} has been unblocked!', + ], + 'blocked_actors' => 'Blocked accounts', + 'blocked_domains' => 'Blocked domains', + 'block_lists_form' => [ + 'handle' => 'Account handle', + 'handle_hint' => 'Input @username@domain account.', + 'domain' => 'Domain name', + 'submit' => 'Block!', + ], + 'list' => [ + 'actor' => 'Account', + 'domain' => 'Domain name', + 'unblock' => 'Unblock', + ], +]; diff --git a/modules/Admin/Language/uk/Home.php b/modules/Admin/Language/uk/Home.php new file mode 100644 index 00000000..6249bab1 --- /dev/null +++ b/modules/Admin/Language/uk/Home.php @@ -0,0 +1,14 @@ + 'Усі подкасти', + 'no_podcast' => 'Подкастів не знайдено', +]; diff --git a/modules/Admin/Language/uk/Install.php b/modules/Admin/Language/uk/Install.php new file mode 100644 index 00000000..814cd8c2 --- /dev/null +++ b/modules/Admin/Language/uk/Install.php @@ -0,0 +1,61 @@ + 'Ручне налаштування', + 'manual_config_subtitle' => + 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'form' => [ + 'instance_config' => 'Instance configuration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', + 'db_hostname' => 'Ім\'я хоста бази даних', + 'db_name' => 'Назва бази даних', + 'db_username' => 'Ім\'я користувача бази даних', + 'db_password' => 'Пароль бази даних', + 'db_prefix' => 'Префікс бази даних', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Обробник кешу', + 'cacheHandlerOptions' => [ + 'file' => 'Файл', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Далі', + 'submit' => 'Завершити установку', + 'create_superadmin' => 'Створіть свій обліковий запис головного адміністратора', + 'email' => 'Пошта', + 'username' => 'Ім\'я користувача', + 'password' => 'Пароль', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Ваш обліковий запис суперадміністратора було успішно створено. Увійдіть, щоб почати подкасти!', + 'databaseConnectError' => + 'Кастопод не зміг підключитись до бази даних. Змініть конфігурацію бази даних і повторіть спробу.', + 'writeError' => + "Не вдалося створити/записати файл `.env`. Ви повинні створити його вручну, перейшовши шаблон файлу `.env.example` в пакеті Castopode.", + ], +]; diff --git a/modules/Admin/Language/uk/Navigation.php b/modules/Admin/Language/uk/Navigation.php new file mode 100644 index 00000000..f3ffb129 --- /dev/null +++ b/modules/Admin/Language/uk/Navigation.php @@ -0,0 +1,44 @@ + 'Toggle sidebar', + 'go_to_website' => 'Go to website', + 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', + 'dashboard' => 'Dashboard', + 'admin' => 'Home', + 'podcasts' => 'Podcasts', + 'podcast-list' => 'All podcasts', + 'podcast-create' => 'New podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', + 'persons' => 'Persons', + 'person-list' => 'All persons', + 'person-create' => 'New person', + 'fediverse' => 'Fediverse', + 'fediverse-blocked-actors' => 'Blocked accounts', + 'fediverse-blocked-domains' => 'Blocked domains', + 'users' => 'Users', + 'user-list' => 'All users', + 'user-create' => 'New user', + 'pages' => 'Pages', + 'page-list' => 'All pages', + 'page-create' => 'New Page', + 'settings' => 'Settings', + 'settings-general' => 'General', + 'settings-theme' => 'Theme', + 'admin-about' => 'About', + 'account' => [ + 'my-account' => 'My account', + 'change-password' => 'Change password', + 'logout' => 'Logout', + ], +]; diff --git a/modules/Admin/Language/uk/Notifications.php b/modules/Admin/Language/uk/Notifications.php new file mode 100644 index 00000000..2b139d51 --- /dev/null +++ b/modules/Admin/Language/uk/Notifications.php @@ -0,0 +1,19 @@ + 'Notifications', + 'reply' => '{actor_username} replied to your post', + 'favourite' => '{actor_username} favourited your post', + 'reblog' => '{actor_username} shared your post', + 'follow' => '{actor_username} started following you', + 'no_notifications' => 'No notifications', + 'mark_all_as_read' => 'Mark all as read', +]; diff --git a/modules/Admin/Language/uk/Page.php b/modules/Admin/Language/uk/Page.php new file mode 100644 index 00000000..b6f49de5 --- /dev/null +++ b/modules/Admin/Language/uk/Page.php @@ -0,0 +1,30 @@ + 'Back to home', + 'page' => 'Page', + 'all_pages' => 'All pages', + 'create' => 'New page', + 'go_to_page' => 'Go to page', + 'edit' => 'Edit page', + 'delete' => 'Delete page', + 'form' => [ + 'title' => 'Title', + 'permalink' => 'Permalink', + 'content' => 'Content', + 'submit_create' => 'Create page', + 'submit_edit' => 'Save', + ], + 'messages' => [ + 'createSuccess' => 'The page “{pageTitle}” was created successfully!', + 'editSuccess' => 'The page was successfully updated!', + ], +]; diff --git a/modules/Admin/Language/uk/Pager.php b/modules/Admin/Language/uk/Pager.php new file mode 100644 index 00000000..c940f604 --- /dev/null +++ b/modules/Admin/Language/uk/Pager.php @@ -0,0 +1,21 @@ + 'Навігація між сторінками', + 'first' => 'Перший', + 'previous' => 'Попередній', + 'next' => 'Наступний', + 'last' => 'Останній', + 'older' => 'Старші', + 'newer' => 'Новіші', + 'invalidTemplate' => '{0} не є правильним шаблоном Пейджера.', + 'invalidPaginationGroup' => '{0} - некоректна група нумерацій.', +]; diff --git a/modules/Admin/Language/uk/Person.php b/modules/Admin/Language/uk/Person.php new file mode 100644 index 00000000..a652be9f --- /dev/null +++ b/modules/Admin/Language/uk/Person.php @@ -0,0 +1,65 @@ + 'Persons', + 'all_persons' => 'All persons', + 'no_person' => 'Nobody found!', + 'create' => 'Create a person', + 'view' => 'View person', + 'edit' => 'Edit person', + 'delete' => 'Delete person', + 'messages' => [ + 'createSuccess' => 'Person has been successfully created!', + 'editSuccess' => 'Person has been successfully updated!', + 'deleteSuccess' => 'Person has been removed!', + ], + 'form' => [ + 'avatar' => 'Avatar', + 'avatar_size_hint' => + 'Avatar must be squared and at least 400px wide and tall.', + 'full_name' => 'Full name', + 'full_name_hint' => 'This is the full name or alias of the person.', + 'unique_name' => 'Unique name', + 'unique_name_hint' => 'Used for URLs', + 'information_url' => 'Information URL', + 'information_url_hint' => + 'Url to a relevant resource of information about the person, such as a homepage or third-party profile platform.', + 'submit_create' => 'Create person', + 'submit_edit' => 'Save person', + ], + 'podcast_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this podcast', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'episode_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this episode', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'credits' => 'Credits', +]; diff --git a/modules/Admin/Language/uk/Platforms.php b/modules/Admin/Language/uk/Platforms.php new file mode 100644 index 00000000..e161181c --- /dev/null +++ b/modules/Admin/Language/uk/Platforms.php @@ -0,0 +1,43 @@ + [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', + 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', + 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', + 'visible' => 'Display in podcast homepage?', + 'on_embed' => 'Display on embeddable player?', + 'remove' => 'Remove {platformName}', + 'submit' => 'Save', + 'messages' => [ + 'updateSuccess' => 'Platform links have been successfully updated!', + 'removeLinkSuccess' => 'The platform link has been removed.', + 'removeLinkError' => + 'The platform link could not be removed. Try again.', + ], + 'description' => [ + 'podcasting' => 'The podcast ID on this platform', + 'social' => 'The podcast account ID on this platform', + 'funding' => 'Call to action message', + ], +]; diff --git a/modules/Admin/Language/uk/Podcast.php b/modules/Admin/Language/uk/Podcast.php new file mode 100644 index 00000000..ff0daebc --- /dev/null +++ b/modules/Admin/Language/uk/Podcast.php @@ -0,0 +1,330 @@ + 'All podcasts', + 'no_podcast' => 'No podcast found!', + 'create' => 'Create podcast', + 'import' => 'Import podcast', + 'all_imports' => 'Podcast imports', + 'new_episode' => 'New Episode', + 'view' => 'View podcast', + 'edit' => 'Edit podcast', + 'publish' => 'Publish podcast', + 'publish_edit' => 'Edit publication', + 'delete' => 'Delete podcast', + 'see_episodes' => 'See episodes', + 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', + 'go_to_page' => 'Go to page', + 'latest_episodes' => 'Latest episodes', + 'see_all_episodes' => 'See all episodes', + 'draft' => 'Draft', + 'messages' => [ + 'createSuccess' => 'Podcast successfully created!', + 'editSuccess' => 'Podcast has been successfully updated!', + 'importSuccess' => 'Podcast has been successfully imported!', + 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', + 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, + cover {cover} + banner {banner} + other {media} + }.', + 'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.', + 'podcastFeedUpdateSuccess' => 'Successful update: {number_of_new_episodes, plural, + one {# episode was} + other {# episodes were} + } added to the podcast!', + 'podcastFeedUpToDate' => 'Podcast is already up to date.', + 'publishError' => 'This podcast is either already published or scheduled for publication.', + 'publishEditError' => 'This podcast is not scheduled for publication.', + 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', + 'scheduleDateError' => 'Schedule date must be set!', + ], + 'form' => [ + 'identity_section_title' => 'Podcast identity', + 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + + 'cover' => 'Podcast cover', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'banner' => 'Podcast banner', + 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', + 'banner_delete' => 'Delete podcast banner', + 'title' => 'Title', + 'handle' => 'Handle', + 'handle_hint' => + 'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.', + 'type' => [ + 'label' => 'Type', + 'episodic' => 'Episodic', + 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', + 'serial' => 'Serial', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + ], + 'description' => 'Description', + 'classification_section_title' => 'Classification', + 'classification_section_subtitle' => + 'These fields will impact your audience and competition.', + 'language' => 'Language', + 'category' => 'Category', + 'category_placeholder' => 'Select a category…', + 'other_categories' => 'Other categories', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does it contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'author_section_title' => 'Author', + 'author_section_subtitle' => 'Who is managing the podcast?', + 'owner_name' => 'Owner name', + 'owner_name_hint' => + 'For administrative use only. Visible in the public RSS feed.', + 'owner_email' => 'Owner email', + 'owner_email_hint' => + 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', + 'publisher' => 'Publisher', + 'publisher_hint' => + 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', + 'copyright' => 'Copyright', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this podcast about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real place or fictional', + 'monetization_section_title' => 'Monetization', + 'monetization_section_subtitle' => + 'Earn money thanks to your audience.', + 'premium' => 'Premium', + 'premium_by_default' => 'Episodes must be set as premium by default', + 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', + 'payment_pointer' => 'Payment Pointer for Web Monetization', + 'payment_pointer_hint' => + 'This is your where you will receive money thanks to Web Monetization', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the podcast', + 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', + 'new_feed_url' => 'New feed URL', + 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', + 'old_feed_url' => 'Old feed URL', + 'partnership' => 'Partnership', + 'partner_id' => 'ID', + 'partner_link_url' => 'Link URL', + 'partner_image_url' => 'Image URL', + 'partner_id_hint' => 'Your own partner ID', + 'partner_link_url_hint' => 'The generic partner link address', + 'partner_image_url_hint' => 'The generic partner image address', + 'block' => 'Podcast should be hidden from public catalogues', + 'block_hint' => + 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'complete' => 'Podcast will not be having new episodes', + 'lock' => 'Prevent podcast from being copied', + 'lock_hint' => + 'The purpose is to tell other podcast platforms whether they are allowed to import this feed. A value of yes means that any attempt to import this feed into a new platform should be rejected.', + 'submit_create' => 'Create podcast', + 'submit_edit' => 'Save podcast', + ], + 'category_options' => [ + 'uncategorized' => 'uncategorized', + 'arts' => 'Arts', + 'business' => 'Business', + 'comedy' => 'Comedy', + 'education' => 'Education', + 'fiction' => 'Fiction', + 'government' => 'Government', + 'health_and_fitness' => 'Health & Fitness', + 'history' => 'History', + 'kids_and_family' => 'Kids & Family', + 'leisure' => 'Leisure', + 'music' => 'Music', + 'news' => 'News', + 'religion_and_spirituality' => 'Religion & Spirituality', + 'science' => 'Science', + 'society_and_culture' => 'Society & Culture', + 'sports' => 'Sports', + 'technology' => 'Technology', + 'true_crime' => 'True Crime', + 'tv_and_film' => 'TV & Film', + 'books' => 'Books', + 'design' => 'Design', + 'fashion_and_beauty' => 'Fashion & Beauty', + 'food' => 'Food', + 'performing_arts' => 'Performing Arts', + 'visual_arts' => 'Visual Arts', + 'careers' => 'Careers', + 'entrepreneurship' => 'Entrepreneurship', + 'investing' => 'Investing', + 'management' => 'Management', + 'marketing' => 'Marketing', + 'non_profit' => 'Non-Profit', + 'comedy_interviews' => 'Comedy Interviews', + 'improv' => 'Improv', + 'stand_up' => 'Stand-Up', + 'courses' => 'Courses', + 'how_to' => 'How To', + 'language_learning' => 'Language Learning', + 'self_improvement' => 'Self-Improvement', + 'comedy_fiction' => 'Comedy Fiction', + 'drama' => 'Drama', + 'science_fiction' => 'Science Fiction', + 'alternative_health' => 'Alternative Health', + 'fitness' => 'Fitness', + 'medicine' => 'Medicine', + 'mental_health' => 'Mental Health', + 'nutrition' => 'Nutrition', + 'sexuality' => 'Sexuality', + 'education_for_kids' => 'Education for Kids', + 'parenting' => 'Parenting', + 'pets_and_animals' => 'Pets & Animals', + 'stories_for_kids' => 'Stories for Kids', + 'animation_and_manga' => 'Animation & Manga', + 'automotive' => 'Automotive', + 'aviation' => 'Aviation', + 'crafts' => 'Crafts', + 'games' => 'Games', + 'hobbies' => 'Hobbies', + 'home_and_garden' => 'Home & Garden', + 'video_games' => 'Video Games', + 'music_commentary' => 'Music Commentary', + 'music_history' => 'Music History', + 'music_interviews' => 'Music Interviews', + 'business_news' => 'Business News', + 'daily_news' => 'Daily News', + 'entertainment_news' => 'Entertainment News', + 'news_commentary' => 'News Commentary', + 'politics' => 'Politics', + 'sports_news' => 'Sports News', + 'tech_news' => 'Tech News', + 'buddhism' => 'Buddhism', + 'christianity' => 'Christianity', + 'hinduism' => 'Hinduism', + 'islam' => 'Islam', + 'judaism' => 'Judaism', + 'religion' => 'Religion', + 'spirituality' => 'Spirituality', + 'astronomy' => 'Astronomy', + 'chemistry' => 'Chemistry', + 'earth_sciences' => 'Earth Sciences', + 'life_sciences' => 'Life Sciences', + 'mathematics' => 'Mathematics', + 'natural_sciences' => 'Natural Sciences', + 'nature' => 'Nature', + 'physics' => 'Physics', + 'social_sciences' => 'Social Sciences', + 'documentary' => 'Documentary', + 'personal_journals' => 'Personal Journals', + 'philosophy' => 'Philosophy', + 'places_and_travel' => 'Places & Travel', + 'relationships' => 'Relationships', + 'baseball' => 'Baseball', + 'basketball' => 'Basketball', + 'cricket' => 'Cricket', + 'fantasy_sports' => 'Fantasy Sports', + 'football' => 'Football', + 'golf' => 'Golf', + 'hockey' => 'Hockey', + 'rugby' => 'Rugby', + 'running' => 'Running', + 'soccer' => 'Soccer', + 'swimming' => 'Swimming', + 'tennis' => 'Tennis', + 'volleyball' => 'Volleyball', + 'wilderness' => 'Wilderness', + 'wrestling' => 'Wrestling', + 'after_shows' => 'After Shows', + 'film_history' => 'Film History', + 'film_interviews' => 'Film Interviews', + 'film_reviews' => 'Film Reviews', + 'tv_reviews' => 'TV Reviews', + ], + 'publish_form' => [ + 'back_to_podcast_dashboard' => 'Back to podcast dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'submit' => 'Publish', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', + 'message_warning_submit' => 'Publish anyway', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'not_published' => 'This podcast is not yet published.', + 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", + 'understand' => 'I understand, I want the podcast to be permanently deleted', + 'submit' => 'Delete', + ], + 'by' => 'By {publisher}', + 'season' => 'Season {seasonNumber}', + 'list_of_episodes_year' => '{year} episodes ({episodeCount})', + 'list_of_episodes_season' => + 'Season {seasonNumber} episodes ({episodeCount})', + 'no_episode' => 'No episode found!', + 'follow' => 'Follow', + 'followers' => '{numberOfFollowers, plural, + one {# follower} + other {# followers} + }', + 'posts' => '{numberOfPosts, plural, + one {# post} + other {# posts} + }', + 'activity' => 'Activity', + 'episodes' => 'Episodes', + 'sponsor' => 'Sponsor', + 'funding_links' => 'Funding links for {podcastTitle}', + 'find_on' => 'Find {podcastTitle} on', + 'listen_on' => 'Listen on', +]; diff --git a/modules/Admin/Language/uk/PodcastNavigation.php b/modules/Admin/Language/uk/PodcastNavigation.php new file mode 100644 index 00000000..bb777707 --- /dev/null +++ b/modules/Admin/Language/uk/PodcastNavigation.php @@ -0,0 +1,42 @@ + 'Go to podcast page', + 'rss_feed' => 'RSS feed', + 'dashboard' => 'Podcast dashboard', + 'podcast-view' => 'Home', + 'podcast-edit' => 'Edit podcast', + 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', + 'episodes' => 'Episodes', + 'episode-list' => 'All episodes', + 'episode-create' => 'New episode', + 'analytics' => 'Analytics', + 'podcast-analytics' => 'Audience overview', + 'podcast-analytics-webpages' => 'Web pages visits', + 'podcast-analytics-locations' => 'Locations', + 'podcast-analytics-unique-listeners' => 'Unique listeners', + 'podcast-analytics-players' => 'Players', + 'podcast-analytics-listening-time' => 'Listening time', + 'podcast-analytics-time-periods' => 'Time periods', + 'monetization' => 'Monetization', + 'subscription-list' => 'All subscriptions', + 'subscription-create' => 'Add subscription', + 'contributors' => 'Contributors', + 'contributor-list' => 'All contributors', + 'contributor-add' => 'Add contributor', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', + 'platforms-social' => 'Social networks', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', +]; diff --git a/modules/Admin/Language/uk/Settings.php b/modules/Admin/Language/uk/Settings.php new file mode 100644 index 00000000..4a70dcba --- /dev/null +++ b/modules/Admin/Language/uk/Settings.php @@ -0,0 +1,58 @@ + 'General settings', + 'instance' => [ + 'title' => 'Instance', + 'site_icon' => 'Site icon', + 'site_icon_delete' => 'Delete site icon', + 'site_icon_hint' => 'Site icons are what you see on your browser tabs, bookmarks bar, and when you add a website as a shortcut on mobile devices.', + 'site_icon_helper' => 'Icon must be squared and at least 512px wide and tall.', + 'site_name' => 'Site name', + 'site_description' => 'Site description', + 'submit' => 'Save', + 'editSuccess' => 'Instance has been updated successfully!', + 'deleteIconSuccess' => 'Site icon has been remove successfully!', + ], + 'images' => [ + 'title' => 'Images', + 'subtitle' => 'Here you can regenerate all images based on the originals that were uploaded. To be used if you find that some images are missing. This task may take a while.', + 'regenerate' => 'Regenerate images', + 'regenerationSuccess' => 'All images have been regenerated successfully!', + ], + 'housekeeping' => [ + 'title' => 'Housekeeping', + 'subtitle' => 'Runs various housekeeping tasks. Use this feature if you ever encounter issues with media files or data integrity. These tasks may take a while.', + 'reset_counts' => 'Reset counts', + 'reset_counts_helper' => 'This option will recalculate and reset all data counts (number of followers, posts, comments, …).', + 'rewrite_media' => 'Rewrite media metadata', + 'rewrite_media_helper' => 'This option will delete all superfluous media files and recreate them (images, audio files, transcripts, chapters, …)', + 'rename_episodes_files' => 'Rename episode audio files', + 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', + 'clear_cache' => 'Clear all cache', + 'clear_cache_helper' => 'This option will flush redis cache or writable/cache files.', + 'run' => 'Run housekeeping', + 'runSuccess' => 'Housekeeping has been run successfully!', + ], + 'theme' => [ + 'title' => 'Theme', + 'accent_section_title' => 'Accent color', + 'accent_section_subtitle' => 'Choose the color to determine the look and feel of all public pages.', + 'pine' => 'Pine', + 'crimson' => 'Crimson', + 'amber' => 'Amber', + 'lake' => 'Lake', + 'jacaranda' => 'Jacaranda', + 'onyx' => 'Onyx', + 'submit' => 'Save', + 'setInstanceThemeSuccess' => 'Theme has been updated successfully!', + ], +]; diff --git a/modules/Admin/Language/uk/Soundbite.php b/modules/Admin/Language/uk/Soundbite.php new file mode 100644 index 00000000..a3f828fe --- /dev/null +++ b/modules/Admin/Language/uk/Soundbite.php @@ -0,0 +1,31 @@ + [ + 'title' => 'Soundbites', + 'soundbite' => 'Soundbite', + ], + 'messages' => [ + 'createSuccess' => 'Soundbite has been successfully created!', + 'deleteSuccess' => 'Soundbite has been successfully removed!', + ], + 'form' => [ + 'title' => 'New soundbite', + 'soundbite_title' => 'Soundbite title', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'submit' => 'Create soundbite', + ], + 'play' => 'Play soundbite', + 'stop' => 'Stop soundbite', + 'create' => 'New soundbite', + 'delete' => 'Delete soundbite', +]; diff --git a/modules/Admin/Language/uk/Validation.php b/modules/Admin/Language/uk/Validation.php new file mode 100644 index 00000000..f76c3163 --- /dev/null +++ b/modules/Admin/Language/uk/Validation.php @@ -0,0 +1,17 @@ + + '{field} is either not an image, or it is not wide or tall enough.', + 'is_image_ratio' => + '{field} is either not an image or not of the right ratio.', + 'is_json' => '{field} contains invalid JSON.', +]; diff --git a/modules/Admin/Language/uk/VideoClip.php b/modules/Admin/Language/uk/VideoClip.php new file mode 100644 index 00000000..638de697 --- /dev/null +++ b/modules/Admin/Language/uk/VideoClip.php @@ -0,0 +1,72 @@ + [ + 'title' => 'Video clips', + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Clip is waiting to be processed.', + 'pending' => 'pending', + 'pending_hint' => 'Clip will be generated shortly.', + 'running' => 'running', + 'running_hint' => 'Clip is being generated.', + 'failed' => 'failed', + 'failed_hint' => 'Clip could not be generated: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Clip was generated successfully!', + ], + 'clip' => 'Clip', + 'duration' => 'Job duration', + ], + 'title' => 'Video clip: {videoClipLabel}', + 'download_clip' => 'Download clip', + 'create' => 'New video clip', + 'go_to_page' => 'Go to clip page', + 'retry' => 'Retry clip generation', + 'delete' => 'Delete clip', + 'logs' => 'Job logs', + 'messages' => [ + 'alreadyExistingError' => 'The video clip you are trying to create already exists!', + 'addToQueueSuccess' => 'Video clip has been added to queue, awaiting to be created!', + 'deleteSuccess' => 'Video clip has been successfully removed!', + ], + 'format' => [ + 'landscape' => 'Landscape', + 'portrait' => 'Portrait', + 'squared' => 'Squared', + ], + 'form' => [ + 'title' => 'New video clip', + 'params_section_title' => 'Video clip parameters', + 'clip_title' => 'Clip title', + 'format' => [ + 'label' => 'Choose a format', + 'landscape_hint' => 'With a 16:9 ratio, landscape videos are great for PeerTube, Youtube and Vimeo.', + 'portrait_hint' => 'With a 9:16 ratio, portrait videos are great for TikTok, Youtube shorts and Instagram stories.', + 'squared_hint' => 'With a 1:1 ratio, squared videos are great for Mastodon, Facebook, Twitter and LinkedIn.', + ], + 'theme' => 'Select a theme', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'trim_start' => 'Trim start', + 'trim_end' => 'Trim end', + 'submit' => 'Create video clip', + ], + 'requirements' => [ + 'title' => 'Missing requirements', + 'missing' => 'You have missing requirements. Make sure to add all the required items to be allowed creating a video for this episode!', + 'ffmpeg' => 'FFmpeg', + 'gd' => 'Graphics Draw (GD)', + 'freetype' => 'Freetype library for GD', + 'transcript' => 'Transcript file (.srt)', + ], +]; diff --git a/modules/Admin/Language/zh-Hans/PodcastImport.php b/modules/Admin/Language/zh-Hans/PodcastImport.php deleted file mode 100644 index ad5732e6..00000000 --- a/modules/Admin/Language/zh-Hans/PodcastImport.php +++ /dev/null @@ -1,37 +0,0 @@ - - '此过程可能需要很长时间。 由于当前版本在运行时未显示任何进度,因此在完成之前您不会看到任何提示。 在超时错误的情况下,增加 `max_execution_time` 值。', - 'old_podcast_section_title' => '要导入的播客', - 'old_podcast_section_subtitle' => - '请确保您在导入之前拥有此播客的权限。 在没有权限的情况下复制和广播播客是盗版行为,可能受到起诉。', - 'imported_feed_url' => '订阅源的 URL', - 'imported_feed_url_hint' => '订阅源必须是 xml 或 rss 格式。', - 'new_podcast_section_title' => '新播客', - 'advanced_params_section_title' => '高级参数', - 'advanced_params_section_subtitle' => - '如果您不知道这些字段的用途,请保留默认值。', - 'slug_field' => '用于计算剧集 Slug 的字段', - 'description_field' => - '用于剧集描述/节目说明的源字段', - 'force_renumber' => '强制剧集重新编号', - 'force_renumber_hint' => - '如果你的播客没有剧集编号但希望在导入时设置,请使用此选项。', - 'season_number' => '季号', - 'season_number_hint' => - '如果您的播客没有季号,但希望在导入时设置,请使用此选项,否则留空。', - 'max_episodes' => '要导入的最大剧集数', - 'max_episodes_hint' => '留空导入所有剧集', - 'lock_import' => - '此订阅源受到保护。你不能导入它。如果你是所有者,请在原平台取消保护。', - 'submit' => '导入播客', -]; diff --git a/modules/Admin/Language/zh-hans/AboutCastopod.php b/modules/Admin/Language/zh-hans/AboutCastopod.php new file mode 100644 index 00000000..9817b219 --- /dev/null +++ b/modules/Admin/Language/zh-hans/AboutCastopod.php @@ -0,0 +1,22 @@ + '关于 Castopod', + 'host_name' => '主机名', + 'version' => 'Castopod 版本', + 'php_version' => 'PHP 版本', + 'os' => '操作系统', + 'languages' => '语言', + 'update_database' => '更新数据库', + 'messages' => [ + 'databaseUpdateSuccess' => '数据库是最新的!', + ], +]; diff --git a/modules/Admin/Language/zh-Hans/Breadcrumb.php b/modules/Admin/Language/zh-hans/Breadcrumb.php similarity index 81% rename from modules/Admin/Language/zh-Hans/Breadcrumb.php rename to modules/Admin/Language/zh-hans/Breadcrumb.php index 80105ec3..db129570 100644 --- a/modules/Admin/Language/zh-Hans/Breadcrumb.php +++ b/modules/Admin/Language/zh-hans/Breadcrumb.php @@ -19,24 +19,29 @@ return [ 'pages' => '页', 'settings' => '设置', 'theme' => '主题', + 'about' => '关于', 'add' => '添加', 'new' => '新建', 'edit' => '编辑', 'persons' => '人', 'publish' => '发布', 'publish-edit' => '编辑发布', - 'publish-date-edit' => 'edit publication date', + 'publish-date-edit' => '编辑发布日期', 'unpublish' => '取消发布', 'delete' => '删除', + 'remove' => '移除', 'fediverse' => '联邦宇宙', - 'block-lists' => '封禁列表', + 'blocked-actors' => '已屏蔽演员', + 'blocked-domains' => '已屏蔽域名', 'users' => '用户', 'my-account' => '我的帐户', 'change-password' => '修改密码', - 'import' => '订阅源导入', + 'imports' => '导入', + 'sync-feeds' => 'synchronize feeds', 'platforms' => '平台', 'social' => '社交网络', 'funding' => '资金支持', + 'monetization-other' => 'other monetization', 'analytics' => '统计数据', 'locations' => '位置', 'webpages' => '网页', diff --git a/modules/Admin/Language/zh-Hans/Charts.php b/modules/Admin/Language/zh-hans/Charts.php similarity index 96% rename from modules/Admin/Language/zh-Hans/Charts.php rename to modules/Admin/Language/zh-hans/Charts.php index e623691a..e1da7b26 100644 --- a/modules/Admin/Language/zh-Hans/Charts.php +++ b/modules/Admin/Language/zh-hans/Charts.php @@ -37,4 +37,5 @@ return [ 'podcast_by_bandwidth' => '每日使用带宽(MB)', 'total_storage_by_month' => '每月存储量 (MB)', 'total_bandwidth_by_month' => '每月使用带宽(MB)', + 'total_bandwidth_by_month_limit' => '每月限制为 {totalBandwidth}', ]; diff --git a/modules/Admin/Language/zh-Hans/Common.php b/modules/Admin/Language/zh-hans/Common.php similarity index 98% rename from modules/Admin/Language/zh-Hans/Common.php rename to modules/Admin/Language/zh-hans/Common.php index 1ecaf542..5909a764 100644 --- a/modules/Admin/Language/zh-Hans/Common.php +++ b/modules/Admin/Language/zh-hans/Common.php @@ -40,6 +40,7 @@ return [ ], 'upload_file' => '上传文件', 'remote_url' => '远程网址', + 'save' => 'Save', ], 'play_episode_button' => [ 'play' => '播放', diff --git a/modules/Admin/Language/zh-Hans/Countries.php b/modules/Admin/Language/zh-hans/Countries.php similarity index 100% rename from modules/Admin/Language/zh-Hans/Countries.php rename to modules/Admin/Language/zh-hans/Countries.php diff --git a/modules/Admin/Language/zh-Hans/Dashboard.php b/modules/Admin/Language/zh-hans/Dashboard.php similarity index 100% rename from modules/Admin/Language/zh-Hans/Dashboard.php rename to modules/Admin/Language/zh-hans/Dashboard.php diff --git a/modules/Admin/Language/zh-Hans/Episode.php b/modules/Admin/Language/zh-hans/Episode.php similarity index 82% rename from modules/Admin/Language/zh-Hans/Episode.php rename to modules/Admin/Language/zh-hans/Episode.php index 7a8f335a..49061df4 100644 --- a/modules/Admin/Language/zh-Hans/Episode.php +++ b/modules/Admin/Language/zh-hans/Episode.php @@ -9,8 +9,8 @@ declare(strict_types=1); */ return [ - 'season' => '第 {seasonNumber} 季', - 'season_abbr' => '第 {seasonNumber} 季', + 'season' => '第 {seasonNumber} 节', + 'season_abbr' => '第 {seasonNumber} 节', 'number' => '第 {episodeNumber} 集', 'number_abbr' => '第 {episodeNumber} 集', 'season_episode' => '第 {seasonNumber} 季第 {episodeNumber} 集', @@ -22,16 +22,17 @@ return [ 'all_podcast_episodes' => '所有播客剧集', 'back_to_podcast' => '返回播客', 'edit' => '编辑', + 'preview' => 'Preview', 'publish' => '发布', 'publish_edit' => '编辑发布', - 'publish_date_edit' => 'Edit publication date', + 'publish_date_edit' => '编辑发布日期', 'unpublish' => '取消发布', 'publish_error' => '剧集已被发布。', 'publish_edit_error' => '剧集已被发布。', 'publish_cancel_error' => '剧集已被发布。', - 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', - 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', - 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'publish_date_edit_error' => '剧集尚未发布,你不能编辑其发布日期。', + 'publish_date_edit_future_error' => '剧集的发布日期只能设置为过去的日期! 如果你想重新安排日期,请先取消发布。', + 'publish_date_edit_success' => '剧集的发布日期已成功更新!', 'unpublish_error' => '剧集尚未发布。', 'delete' => '删除', 'go_to_page' => '转到页面', @@ -55,6 +56,7 @@ return [ }', 'episode' => '剧集', 'visibility' => '可见性', + 'downloads' => '下载', 'comments' => '评论', 'actions' => '操作', ], @@ -79,13 +81,13 @@ return [ audio {音频} other {媒体} }', - 'deleteFileError' => '无法删除 {type, select, - transcript {字幕} - chapters {章节} - image {封面} - audio {音频} - other {媒体} - } 文件 {file_path}。您可以手动将其从磁盘删除。', + 'deleteFileError' => 'Failed to delete {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => '选中的剧集已存在。', ], 'form' => [ @@ -137,9 +139,9 @@ return [ 'location_name' => '位置名称或地址', 'location_name_hint' => '真或假位置都可以', 'transcript' => '字幕(字幕/隐藏字幕)', - 'transcript_hint' => '仅允许使用 .srt。', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', 'transcript_download' => '下载字幕', - 'transcript_file' => '字幕文件(.srt)', + 'transcript_file' => 'Transcript file (.srt or .vtt)', 'transcript_remote_url' => '用于字幕的网址', 'transcript_file_delete' => '删除字幕文件', 'chapters' => '章节', @@ -150,8 +152,8 @@ return [ 'chapters_file_delete' => '删除章节文件', 'advanced_section_title' => '高级参数', 'advanced_section_subtitle' => - '如果你不需要 Castopod 处理某些订阅源标签,请在此处设置。', - 'custom_rss' => '剧集的自定义订阅标签', + '如果您需要 Castopod 无法处理的 RSS 标签,请在此处设置它们。', + 'custom_rss' => '剧集的自定义 RSS 标签', 'custom_rss_hint' => '这将被注入到 ❬item❭ 标签中。', 'block' => '剧集应该在公共目录中隐藏', 'block_hint' => @@ -183,13 +185,13 @@ return [ 'message_warning_submit' => '仍然发布', ], 'publish_date_edit_form' => [ - 'new_publication_date' => 'New publication date', - 'new_publication_date_hint' => 'Must be set to a past date.', - 'submit' => 'Edit publication date', + 'new_publication_date' => '新发布日期', + 'new_publication_date_hint' => '必须设置为过去的日期。', + 'submit' => '编辑发布日期', ], 'unpublish_form' => [ 'disclaimer' => - "取消发布该剧集将删除相关的所有评论和播文,并将其从播客的订阅源中删除。", + "取消发布该剧集将删除相关的所有评论和播文,并将其从播客的 RSS 摘要中删除。", 'understand' => '我明白,我想取消发布此剧集', 'submit' => '取消发布', ], @@ -210,4 +212,14 @@ return [ 'light' => '亮色', 'light-transparent' => '亮色透明', ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], ]; diff --git a/modules/Admin/Language/zh-Hans/EpisodeNavigation.php b/modules/Admin/Language/zh-hans/EpisodeNavigation.php similarity index 100% rename from modules/Admin/Language/zh-Hans/EpisodeNavigation.php rename to modules/Admin/Language/zh-hans/EpisodeNavigation.php diff --git a/modules/Admin/Language/zh-Hans/Fediverse.php b/modules/Admin/Language/zh-hans/Fediverse.php similarity index 100% rename from modules/Admin/Language/zh-Hans/Fediverse.php rename to modules/Admin/Language/zh-hans/Fediverse.php diff --git a/modules/Admin/Language/zh-Hans/Home.php b/modules/Admin/Language/zh-hans/Home.php similarity index 100% rename from modules/Admin/Language/zh-Hans/Home.php rename to modules/Admin/Language/zh-hans/Home.php diff --git a/modules/Admin/Language/zh-Hans/Install.php b/modules/Admin/Language/zh-hans/Install.php similarity index 100% rename from modules/Admin/Language/zh-Hans/Install.php rename to modules/Admin/Language/zh-hans/Install.php diff --git a/modules/Admin/Language/zh-Hans/Navigation.php b/modules/Admin/Language/zh-hans/Navigation.php similarity index 87% rename from modules/Admin/Language/zh-Hans/Navigation.php rename to modules/Admin/Language/zh-hans/Navigation.php index 9cf34c78..3ed1fccc 100644 --- a/modules/Admin/Language/zh-Hans/Navigation.php +++ b/modules/Admin/Language/zh-hans/Navigation.php @@ -12,12 +12,14 @@ return [ 'toggle_sidebar' => '切换侧边栏', 'go_to_website' => '访问网站', 'go_to_admin' => '登录到管理员', + 'not-authorized' => 'Not authorized', 'dashboard' => '控制面板', 'admin' => '主页', 'podcasts' => '播客', 'podcast-list' => '全部播客', 'podcast-create' => '新播客', - 'podcast-import' => '导入播客', + 'all-podcast-imports' => '全部播客导入', + 'podcast-imports-add' => '导入播客', 'persons' => '人员', 'person-list' => '所有人', 'person-create' => '新成员', @@ -33,6 +35,7 @@ return [ 'settings' => '设置', 'settings-general' => '通用', 'settings-theme' => '主题', + 'admin-about' => 'About', 'account' => [ 'my-account' => '我的帐户', 'change-password' => '修改密码', diff --git a/modules/Admin/Language/zh-Hans/Notifications.php b/modules/Admin/Language/zh-hans/Notifications.php similarity index 100% rename from modules/Admin/Language/zh-Hans/Notifications.php rename to modules/Admin/Language/zh-hans/Notifications.php diff --git a/modules/Admin/Language/zh-Hans/Page.php b/modules/Admin/Language/zh-hans/Page.php similarity index 100% rename from modules/Admin/Language/zh-Hans/Page.php rename to modules/Admin/Language/zh-hans/Page.php diff --git a/modules/Admin/Language/zh-Hans/Pager.php b/modules/Admin/Language/zh-hans/Pager.php similarity index 100% rename from modules/Admin/Language/zh-Hans/Pager.php rename to modules/Admin/Language/zh-hans/Pager.php diff --git a/modules/Admin/Language/zh-Hans/Person.php b/modules/Admin/Language/zh-hans/Person.php similarity index 100% rename from modules/Admin/Language/zh-Hans/Person.php rename to modules/Admin/Language/zh-hans/Person.php diff --git a/modules/Admin/Language/zh-Hans/Platforms.php b/modules/Admin/Language/zh-hans/Platforms.php similarity index 68% rename from modules/Admin/Language/zh-Hans/Platforms.php rename to modules/Admin/Language/zh-hans/Platforms.php index 6a71b42a..2869a4b2 100644 --- a/modules/Admin/Language/zh-Hans/Platforms.php +++ b/modules/Admin/Language/zh-hans/Platforms.php @@ -9,9 +9,22 @@ declare(strict_types=1); */ return [ - 'title' => '平台', + 'title' => [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', 'home_url' => '转至 {platformName} 网站', + 'register' => 'Register', 'submit_url' => '在 {platformName} 上提交你的播客', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', 'visible' => '在播客主页上显示?', 'on_embed' => '在嵌入式播放器上显示?', 'remove' => '移除 {platformName}', diff --git a/modules/Admin/Language/zh-Hans/Podcast.php b/modules/Admin/Language/zh-hans/Podcast.php similarity index 83% rename from modules/Admin/Language/zh-Hans/Podcast.php rename to modules/Admin/Language/zh-hans/Podcast.php index 1a145c20..1a47c363 100644 --- a/modules/Admin/Language/zh-Hans/Podcast.php +++ b/modules/Admin/Language/zh-hans/Podcast.php @@ -13,6 +13,7 @@ return [ 'no_podcast' => '没有找到播客!', 'create' => '创建播客', 'import' => '导入播客', + 'all_imports' => '播客导入', 'new_episode' => '新剧集', 'view' => '浏览博客', 'edit' => '编辑播客', @@ -21,6 +22,7 @@ return [ 'delete' => '删除播客', 'see_episodes' => '查看剧集', 'see_contributors' => '查看贡献者', + 'monetization_other' => 'Other monetization', 'go_to_page' => '转到页面', 'latest_episodes' => '最新剧集', 'see_all_episodes' => '查看所有剧集', @@ -48,7 +50,6 @@ return [ other {# 剧集} } 添加到播客!', 'podcastFeedUpToDate' => '播客已经是最新状态。', - 'podcastNotImported' => '播客无法更新,因为它没有被导入。', 'publishError' => '此播客已经发布或计划发布。', 'publishEditError' => '此播客未计划发布。', 'publishCancelSuccess' => '取消播客发布!', @@ -57,6 +58,8 @@ return [ 'form' => [ 'identity_section_title' => '播客标识', 'identity_section_subtitle' => '这些字段可能让你脱颖而出。', + 'fediverse_section_title' => 'Fediverse identity', + 'cover' => '播客封面', 'cover_size_hint' => '封面必须是方形,而且至少 1400 px 宽度和高度。', 'banner' => '播客横幅', @@ -71,7 +74,17 @@ return [ 'episodic' => '剧集', 'episodic_hint' => '如果在没有任何特定情况下进行剧集排序。那么最新剧集优先显示。', 'serial' => '系列', - 'serial_hint' => '如果指定剧集排序方式。那么最久剧集将优先显示。', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', ], 'description' => '描述', 'classification_section_title' => '分类', @@ -92,10 +105,12 @@ return [ 'author_section_subtitle' => '谁在管理播客?', 'owner_name' => '所有者名称', 'owner_name_hint' => - '仅供管理使用,在公开 RSS 提要中可见。', + '仅供管理使用,在公开 RSS 摘要中可见。', 'owner_email' => '所有者邮箱', 'owner_email_hint' => - '大多数平台将使用它来验证播客的所有权。 在公开 RSS 提要中可见。', + '大多数平台将使用它来验证播客的所有权。 在公开 RSS 摘要中可见。', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', 'publisher' => '发布者', 'publisher_hint' => '负责制作节目的小组。 通常指播客的母公司或网络。 有时会被标记为“作者”。', @@ -110,19 +125,25 @@ return [ 'premium' => '高级版', 'premium_by_default' => '剧集必须默认设置为付费会员订阅。', 'premium_by_default_hint' => '默认情况下,播客剧集将被标记为高级。 你仍然可以选择将某些剧集、预告片等设置为公开。', + 'op3' => '打开播客前缀项目 (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => '使用 OP3(一项开源且值得信赖的第三方分析服务)来评估您的分析数据。 与开源播客生态系统共享、验证和比较您的分析数据。', + 'op3_enable' => '启用 OP3 分析服务', + 'op3_enable_hint' => '出于安全原因,高级剧集的分析数据将不会与 OP3 共享。', 'payment_pointer' => '网络货币化支付指南', 'payment_pointer_hint' => '借助网络货币化,你可以在此收款', 'advanced_section_title' => '高级参数', 'advanced_section_subtitle' => - '如果你不需要 Castopod 处理某些订阅源标签,请在此处设置。', - 'custom_rss' => '播客的自定义订阅标签', - 'custom_rss_hint' => '这将被注入到❬channel❭标签中。', - 'new_feed_url' => '新订阅源网址', - 'new_feed_url_hint' => '当你迁移到另一个域或播客托管平台时,请使用此字段。 默认情况下,播客导入时,该值为当前的订阅源网址。', - 'old_feed_url' => '旧订阅源网址', - 'update_feed' => '更新订阅源', - 'update_feed_tip' => '导入此播客的最新剧集', + '如果您需要 Castopod 无法处理的 RSS 标签,请在此处设置它们。', + 'custom_rss' => '播客的自定义 RSS 标签', + 'custom_rss_hint' => '这将被注入到 ❬channel❭ 标签中。', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', + 'new_feed_url' => '新摘要网址', + 'new_feed_url_hint' => '当你迁移到另一个域或播客托管平台时,请使用此字段。 默认情况下,播客导入时,该值为当前的 RSS 网址。', + 'old_feed_url' => '旧摘要网址', 'partnership' => '合作伙伴', 'partner_id' => 'ID', 'partner_link_url' => '链接网址', @@ -130,14 +151,13 @@ return [ 'partner_id_hint' => '你自己的合作伙伴 ID', 'partner_link_url_hint' => '通用合作伙伴链接地址', 'partner_image_url_hint' => '通用合作伙伴图片地址', - 'status_section_title' => '状态', 'block' => '播客应该在公共目录中隐藏', 'block_hint' => '播客显示或隐藏状态:打开此选项可防止整个播客出现在 Apple 播客、Google 播客以及从此目录中提取剧集的任何第三方应用程序中。(不保证)', 'complete' => '播客没有新剧集', 'lock' => '防止播客被盗用', 'lock_hint' => - '目的是告诉其他播客平台是否允许导入此订阅源。 值为是表示拒绝将此订阅源导入任何平台。', + '目的是告诉其他播客平台是否允许导入此摘要。 值为是表示拒绝将此摘要导入任何平台。', 'submit_create' => '创建播客', 'submit_edit' => '保存播客', ], diff --git a/modules/Admin/Language/zh-Hans/PodcastNavigation.php b/modules/Admin/Language/zh-hans/PodcastNavigation.php similarity index 74% rename from modules/Admin/Language/zh-Hans/PodcastNavigation.php rename to modules/Admin/Language/zh-hans/PodcastNavigation.php index 75564b13..0978a9b2 100644 --- a/modules/Admin/Language/zh-Hans/PodcastNavigation.php +++ b/modules/Admin/Language/zh-hans/PodcastNavigation.php @@ -10,10 +10,13 @@ declare(strict_types=1); return [ 'go_to_page' => '转到播客页面', + 'rss_feed' => 'RSS feed', 'dashboard' => '播客控制面板', 'podcast-view' => '主页', 'podcast-edit' => '编辑播客', 'podcast-persons-manage' => '管理人员', + 'podcast-imports' => '播客导入', + 'podcast-imports-sync' => 'Sync feeds', 'episodes' => '剧集', 'episode-list' => '所有剧集', 'episode-create' => '新剧集', @@ -25,14 +28,15 @@ return [ 'podcast-analytics-players' => '播放', 'podcast-analytics-listening-time' => '收听时间', 'podcast-analytics-time-periods' => '时间段', - 'premium' => '高级版', + 'monetization' => 'Monetization', 'subscription-list' => '所有订阅', - 'subscription-add' => '添加订阅', + 'subscription-create' => 'Add subscription', 'contributors' => '贡献者', 'contributor-list' => '所有贡献者', 'contributor-add' => '添加贡献者', - 'platforms' => '外部平台', - 'platforms-podcasting' => '播客', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', 'platforms-social' => '社交网络', - 'platforms-funding' => '资金支持', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', ]; diff --git a/modules/Admin/Language/zh-Hans/Settings.php b/modules/Admin/Language/zh-hans/Settings.php similarity index 100% rename from modules/Admin/Language/zh-Hans/Settings.php rename to modules/Admin/Language/zh-hans/Settings.php diff --git a/modules/Admin/Language/zh-Hans/Soundbite.php b/modules/Admin/Language/zh-hans/Soundbite.php similarity index 100% rename from modules/Admin/Language/zh-Hans/Soundbite.php rename to modules/Admin/Language/zh-hans/Soundbite.php diff --git a/modules/Admin/Language/zh-Hans/Validation.php b/modules/Admin/Language/zh-hans/Validation.php similarity index 76% rename from modules/Admin/Language/zh-Hans/Validation.php rename to modules/Admin/Language/zh-hans/Validation.php index cfdeac6e..4a447d44 100644 --- a/modules/Admin/Language/zh-Hans/Validation.php +++ b/modules/Admin/Language/zh-hans/Validation.php @@ -13,6 +13,5 @@ return [ '{field} 不是一张图片,或者宽或高度不够。', 'is_image_ratio' => '{field} 不是图片或比例不正确。', - 'validate_url' => - '{field} 字段必须是有效的 URL(例如 https://example.com/)。', + 'is_json' => '{field} contains invalid JSON.', ]; diff --git a/modules/Admin/Language/zh-Hans/VideoClip.php b/modules/Admin/Language/zh-hans/VideoClip.php similarity index 100% rename from modules/Admin/Language/zh-Hans/VideoClip.php rename to modules/Admin/Language/zh-hans/VideoClip.php diff --git a/modules/Admin/Language/zh-hant/AboutCastopod.php b/modules/Admin/Language/zh-hant/AboutCastopod.php new file mode 100644 index 00000000..3fb62aff --- /dev/null +++ b/modules/Admin/Language/zh-hant/AboutCastopod.php @@ -0,0 +1,22 @@ + 'About Castopod', + 'host_name' => 'Host name', + 'version' => 'Castopod version', + 'php_version' => 'PHP version', + 'os' => 'Operating System', + 'languages' => 'Languages', + 'update_database' => 'Update database', + 'messages' => [ + 'databaseUpdateSuccess' => 'Database is up to date!', + ], +]; diff --git a/modules/Admin/Language/zh-hant/Breadcrumb.php b/modules/Admin/Language/zh-hant/Breadcrumb.php new file mode 100644 index 00000000..408c9f9f --- /dev/null +++ b/modules/Admin/Language/zh-hant/Breadcrumb.php @@ -0,0 +1,57 @@ + 'breadcrumb', + config('Admin') + ->gateway => 'Home', + 'podcasts' => 'podcasts', + 'episodes' => 'episodes', + 'subscriptions' => 'subscriptions', + 'contributors' => 'contributors', + 'pages' => 'pages', + 'settings' => 'settings', + 'theme' => 'theme', + 'about' => 'about', + 'add' => 'add', + 'new' => 'new', + 'edit' => 'edit', + 'persons' => 'persons', + 'publish' => 'publish', + 'publish-edit' => 'edit publication', + 'publish-date-edit' => 'edit publication date', + 'unpublish' => 'unpublish', + 'delete' => 'delete', + 'remove' => 'remove', + 'fediverse' => 'fediverse', + 'blocked-actors' => 'blocked actors', + 'blocked-domains' => 'blocked domains', + 'users' => 'users', + 'my-account' => 'my account', + 'change-password' => 'change password', + 'imports' => 'imports', + 'sync-feeds' => 'synchronize feeds', + 'platforms' => 'platforms', + 'social' => 'social networks', + 'funding' => 'funding', + 'monetization-other' => 'other monetization', + 'analytics' => 'analytics', + 'locations' => 'locations', + 'webpages' => 'web pages', + 'unique-listeners' => 'unique listeners', + 'players' => 'players', + 'listening-time' => 'listening time', + 'time-periods' => 'time periods', + 'soundbites' => 'soundbites', + 'video-clips' => 'video clips', + 'embed' => 'embeddable player', + 'notifications' => 'notifications', + 'suspend' => 'suspend', +]; diff --git a/modules/Admin/Language/zh-hant/Charts.php b/modules/Admin/Language/zh-hant/Charts.php new file mode 100644 index 00000000..6ede2510 --- /dev/null +++ b/modules/Admin/Language/zh-hant/Charts.php @@ -0,0 +1,41 @@ + 'Episode downloads by service (for the past week)', + 'by_player_weekly' => 'Episode downloads by player (for the past week)', + 'by_player_yearly' => 'Episode downloads by player (for the past year)', + 'by_device_weekly' => 'Episode downloads by device (for the past week)', + 'by_os_weekly' => 'Episode downloads by O.S. (for the past week)', + 'podcast_by_region' => 'Episode downloads by region (for the past week)', + 'unique_daily_listeners' => 'Daily unique listeners', + 'unique_monthly_listeners' => 'Monthly unique listeners', + 'by_browser' => 'Web pages usage by browser (for the past week)', + 'podcast_by_day' => 'Episode daily downloads', + 'podcast_by_month' => 'Episode monthly downloads', + 'episode_by_day' => 'Episode daily downloads (first 60 days)', + 'episode_by_month' => 'Episode monthly downloads', + 'episodes_by_day' => + '5 latest episodes downloads (during their first 60 days)', + 'by_country_weekly' => 'Episode downloads by country (for the past week)', + 'by_country_yearly' => 'Episode downloads by country (for the past year)', + 'by_domain_weekly' => 'Web pages visits by source (for the past week)', + 'by_domain_yearly' => 'Web pages visits by source (for the past year)', + 'by_entry_page' => 'Web pages visits by landing page (for the past week)', + 'podcast_bots' => 'Bots (crawlers)', + 'daily_listening_time' => 'Daily cumulative listening time', + 'monthly_listening_time' => 'Monthly cumulative listening time', + 'by_weekday' => 'By week day (for the past 60 days)', + 'by_hour' => 'By time of day (for the past 60 days)', + 'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)', + 'total_storage_by_month' => 'Monthly storage (in MB)', + 'total_bandwidth_by_month' => 'Monthly used bandwidth (in MB)', + 'total_bandwidth_by_month_limit' => 'Limited to {totalBandwidth} per month', +]; diff --git a/modules/Admin/Language/zh-hant/Common.php b/modules/Admin/Language/zh-hant/Common.php new file mode 100644 index 00000000..74addcf2 --- /dev/null +++ b/modules/Admin/Language/zh-hant/Common.php @@ -0,0 +1,52 @@ + 'Yes', + 'no' => 'No', + 'cancel' => 'Cancel', + 'optional' => 'Optional', + 'more' => 'More', + 'no_data' => 'No data found!', + 'close' => 'Close', + 'edit' => 'Edit', + 'copy' => 'Copy', + 'copied' => 'Copied!', + 'home' => 'Home', + 'explicit' => 'Explicit', + 'powered_by' => 'Powered by {castopod}', + 'actions' => 'Actions', + 'pageInfo' => 'Page {currentPage} out of {pageCount}', + 'go_back' => 'Go back', + 'forms' => [ + 'editor' => [ + 'write' => 'Write', + 'preview' => 'Preview', + 'help' => 'Powered by markdown', + ], + 'multiSelect' => [ + 'selectText' => 'Press to select', + 'loadingText' => 'Loading…', + 'noResultsText' => 'No results found', + 'noChoicesText' => 'No choices to choose from', + 'maxItemText' => 'Cannot add more items', + ], + 'upload_file' => 'Upload a file', + 'remote_url' => 'Remote URL', + 'save' => 'Save', + ], + 'play_episode_button' => [ + 'play' => 'Play', + 'playing' => 'Playing', + ], + 'size_limit' => 'Size limit: {0}.', + 'choose_interact' => 'Choose how to interact', + 'view' => 'View', +]; diff --git a/modules/Admin/Language/zh-hant/Countries.php b/modules/Admin/Language/zh-hant/Countries.php new file mode 100644 index 00000000..4cd5d9c8 --- /dev/null +++ b/modules/Admin/Language/zh-hant/Countries.php @@ -0,0 +1,264 @@ + 'Andorra', + 'AE' => 'United Arab Emirates', + 'AF' => 'Afghanistan', + 'AG' => 'Antigua and Barbuda', + 'AI' => 'Anguilla', + 'AL' => 'Albania', + 'AM' => 'Armenia', + 'AO' => 'Angola', + 'AQ' => 'Antarctica', + 'AR' => 'Argentina', + 'AS' => 'American Samoa', + 'AT' => 'Austria', + 'AU' => 'Australia', + 'AW' => 'Aruba', + 'AX' => 'Åland Islands', + 'AZ' => 'Azerbaijan', + 'BA' => 'Bosnia and Herzegovina', + 'BB' => 'Barbados', + 'BD' => 'Bangladesh', + 'BE' => 'Belgium', + 'BF' => 'Burkina Faso', + 'BG' => 'Bulgaria', + 'BH' => 'Bahrain', + 'BI' => 'Burundi', + 'BJ' => 'Benin', + 'BL' => 'Saint Barthélemy', + 'BM' => 'Bermuda', + 'BN' => 'Brunei Darussalam', + 'BO' => 'Bolivia, Plurinational State of', + 'BQ' => 'Bonaire, Sint Eustatius and Saba', + 'BR' => 'Brazil', + 'BS' => 'Bahamas', + 'BT' => 'Bhutan', + 'BV' => 'Bouvet Island', + 'BW' => 'Botswana', + 'BY' => 'Belarus', + 'BZ' => 'Belize', + 'CA' => 'Canada', + 'CC' => 'Cocos (Keeling) Islands', + 'CD' => 'Congo, the Democratic Republic of the', + 'CF' => 'Central African Republic', + 'CG' => 'Congo', + 'CH' => 'Switzerland', + 'CI' => "Côte d'Ivoire", + 'CK' => 'Cook Islands', + 'CL' => 'Chile', + 'CM' => 'Cameroon', + 'CN' => 'China', + 'CO' => 'Colombia', + 'CR' => 'Costa Rica', + 'CU' => 'Cuba', + 'CV' => 'Cape Verde', + 'CW' => 'Curaçao', + 'CX' => 'Christmas Island', + 'CY' => 'Cyprus', + 'CZ' => 'Czech Republic', + 'DE' => 'Germany', + 'DJ' => 'Djibouti', + 'DK' => 'Denmark', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'DZ' => 'Algeria', + 'EC' => 'Ecuador', + 'EE' => 'Estonia', + 'EG' => 'Egypt', + 'EH' => 'Western Sahara', + 'ER' => 'Eritrea', + 'ES' => 'Spain', + 'ET' => 'Ethiopia', + 'FI' => 'Finland', + 'FJ' => 'Fiji', + 'FK' => 'Falkland Islands (Malvinas)', + 'FM' => 'Micronesia, Federated States of', + 'FO' => 'Faroe Islands', + 'FR' => 'France', + 'GA' => 'Gabon', + 'GB' => 'United Kingdom', + 'GD' => 'Grenada', + 'GE' => 'Georgia', + 'GF' => 'French Guiana', + 'GG' => 'Guernsey', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GL' => 'Greenland', + 'GM' => 'Gambia', + 'GN' => 'Guinea', + 'GP' => 'Guadeloupe', + 'GQ' => 'Equatorial Guinea', + 'GR' => 'Greece', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'GT' => 'Guatemala', + 'GU' => 'Guam', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HK' => 'Hong Kong', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HR' => 'Croatia', + 'HT' => 'Haiti', + 'HU' => 'Hungary', + 'ID' => 'Indonesia', + 'IE' => 'Ireland', + 'IL' => 'Israel', + 'IM' => 'Isle of Man', + 'IN' => 'India', + 'IO' => 'British Indian Ocean Territory', + 'IQ' => 'Iraq', + 'IR' => 'Iran, Islamic Republic of', + 'IS' => 'Iceland', + 'IT' => 'Italy', + 'JE' => 'Jersey', + 'JM' => 'Jamaica', + 'JO' => 'Jordan', + 'JP' => 'Japan', + 'KE' => 'Kenya', + 'KG' => 'Kyrgyzstan', + 'KH' => 'Cambodia', + 'KI' => 'Kiribati', + 'KM' => 'Comoros', + 'KN' => 'Saint Kitts and Nevis', + 'KP' => "Korea, Democratic People's Republic of", + 'KR' => 'Korea, Republic of', + 'KW' => 'Kuwait', + 'KY' => 'Cayman Islands', + 'KZ' => 'Kazakhstan', + 'LA' => "Lao People's Democratic Republic", + 'LB' => 'Lebanon', + 'LC' => 'Saint Lucia', + 'LI' => 'Liechtenstein', + 'LK' => 'Sri Lanka', + 'LR' => 'Liberia', + 'LS' => 'Lesotho', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'LV' => 'Latvia', + 'LY' => 'Libya', + 'MA' => 'Morocco', + 'MC' => 'Monaco', + 'MD' => 'Moldova, Republic of', + 'ME' => 'Montenegro', + 'MF' => 'Saint Martin (French part)', + 'MG' => 'Madagascar', + 'MH' => 'Marshall Islands', + 'MK' => 'Macedonia, the Former Yugoslav Republic of', + 'ML' => 'Mali', + 'MM' => 'Myanmar', + 'MN' => 'Mongolia', + 'MO' => 'Macao', + 'MP' => 'Northern Mariana Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MS' => 'Montserrat', + 'MT' => 'Malta', + 'MU' => 'Mauritius', + 'MV' => 'Maldives', + 'MW' => 'Malawi', + 'MX' => 'Mexico', + 'MY' => 'Malaysia', + 'MZ' => 'Mozambique', + 'N/A' => 'Not Applicable (local IP…)', + 'NA' => 'Namibia', + 'NC' => 'New Caledonia', + 'NE' => 'Niger', + 'NF' => 'Norfolk Island', + 'NG' => 'Nigeria', + 'NI' => 'Nicaragua', + 'NL' => 'Netherlands', + 'NO' => 'Norway', + 'NP' => 'Nepal', + 'NR' => 'Nauru', + 'NU' => 'Niue', + 'NZ' => 'New Zealand', + 'OM' => 'Oman', + 'PA' => 'Panama', + 'PE' => 'Peru', + 'PF' => 'French Polynesia', + 'PG' => 'Papua New Guinea', + 'PH' => 'Philippines', + 'PK' => 'Pakistan', + 'PL' => 'Poland', + 'PM' => 'Saint Pierre and Miquelon', + 'PN' => 'Pitcairn', + 'PR' => 'Puerto Rico', + 'PS' => 'Palestine, State of', + 'PT' => 'Portugal', + 'PW' => 'Palau', + 'PY' => 'Paraguay', + 'QA' => 'Qatar', + 'RE' => 'Réunion', + 'RO' => 'Romania', + 'RS' => 'Serbia', + 'RU' => 'Russian Federation', + 'RW' => 'Rwanda', + 'SA' => 'Saudi Arabia', + 'SB' => 'Solomon Islands', + 'SC' => 'Seychelles', + 'SD' => 'Sudan', + 'SE' => 'Sweden', + 'SG' => 'Singapore', + 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', + 'SI' => 'Slovenia', + 'SJ' => 'Svalbard and Jan Mayen', + 'SK' => 'Slovakia', + 'SL' => 'Sierra Leone', + 'SM' => 'San Marino', + 'SN' => 'Senegal', + 'SO' => 'Somalia', + 'SR' => 'Suriname', + 'SS' => 'South Sudan', + 'ST' => 'Sao Tome and Principe', + 'SV' => 'El Salvador', + 'SX' => 'Sint Maarten (Dutch part)', + 'SY' => 'Syrian Arab Republic', + 'SZ' => 'Swaziland', + 'TC' => 'Turks and Caicos Islands', + 'TD' => 'Chad', + 'TF' => 'French Southern Territories', + 'TG' => 'Togo', + 'TH' => 'Thailand', + 'TJ' => 'Tajikistan', + 'TK' => 'Tokelau', + 'TL' => 'Timor-Leste', + 'TM' => 'Turkmenistan', + 'TN' => 'Tunisia', + 'TO' => 'Tonga', + 'TR' => 'Turkey', + 'TT' => 'Trinidad and Tobago', + 'TV' => 'Tuvalu', + 'TW' => 'Taiwan, Province of China', + 'TZ' => 'Tanzania, United Republic of', + 'UA' => 'Ukraine', + 'UG' => 'Uganda', + 'UM' => 'United States Minor Outlying Islands', + 'US' => 'United States', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VA' => 'Holy See (Vatican City State)', + 'VC' => 'Saint Vincent and the Grenadines', + 'VE' => 'Venezuela, Bolivarian Republic of', + 'VG' => 'Virgin Islands, British', + 'VI' => 'Virgin Islands, U.S.', + 'VN' => 'Viet Nam', + 'VU' => 'Vanuatu', + 'WF' => 'Wallis and Futuna', + 'WS' => 'Samoa', + 'YE' => 'Yemen', + 'YT' => 'Mayotte', + 'ZA' => 'South Africa', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', +]; diff --git a/modules/Admin/Language/zh-hant/Dashboard.php b/modules/Admin/Language/zh-hant/Dashboard.php new file mode 100644 index 00000000..881073fd --- /dev/null +++ b/modules/Admin/Language/zh-hant/Dashboard.php @@ -0,0 +1,28 @@ + 'Admin dashboard', + 'welcome_message' => 'Welcome to the admin area!', + 'podcasts' => [ + 'title' => 'Podcasts', + 'not_found' => 'No published podcast', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'episodes' => [ + 'title' => 'Episodes', + 'not_found' => 'No published episode', + 'last_published' => 'Last published on {lastPublicationDate}', + ], + 'storage' => [ + 'title' => 'Storage', + 'subtitle' => '{totalUploaded} out of {totalStorage}', + ], +]; diff --git a/modules/Admin/Language/zh-hant/Episode.php b/modules/Admin/Language/zh-hant/Episode.php new file mode 100644 index 00000000..4fa846e3 --- /dev/null +++ b/modules/Admin/Language/zh-hant/Episode.php @@ -0,0 +1,225 @@ + 'Season {seasonNumber}', + 'season_abbr' => 'S{seasonNumber}', + 'number' => 'Episode {episodeNumber}', + 'number_abbr' => 'Ep. {episodeNumber}', + 'season_episode' => 'Season {seasonNumber} episode {episodeNumber}', + 'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}', + 'number_of_comments' => '{numberOfComments, plural, + one {# comment} + other {# comments} + }', + 'all_podcast_episodes' => 'All podcast episodes', + 'back_to_podcast' => 'Go back to podcast', + 'edit' => 'Edit', + 'preview' => 'Preview', + 'publish' => 'Publish', + 'publish_edit' => 'Edit publication', + 'publish_date_edit' => 'Edit publication date', + 'unpublish' => 'Unpublish', + 'publish_error' => 'Episode is already published.', + 'publish_edit_error' => 'Episode is already published.', + 'publish_cancel_error' => 'Episode is already published.', + 'publish_date_edit_error' => 'Episode has not been published yet, you cannot edit its publication date.', + 'publish_date_edit_future_error' => 'Episode\'s publication date can only be set to a past date! If you would like to reschedule it, unpublish it first.', + 'publish_date_edit_success' => 'Episode\'s publication date has been updated successfully!', + 'unpublish_error' => 'Episode is not published.', + 'delete' => 'Delete', + 'go_to_page' => 'Go to page', + 'create' => 'Add an episode', + 'publication_status' => [ + 'published' => 'Published', + 'with_podcast' => 'Published', + 'scheduled' => 'Scheduled', + 'not_published' => 'Not published', + ], + 'with_podcast_hint' => 'To be published at the same time as the podcast', + 'list' => [ + 'search' => [ + 'placeholder' => 'Search for an episode', + 'clear' => 'Clear search', + 'submit' => 'Search', + ], + 'number_of_episodes' => '{numberOfEpisodes, plural, + one {# episode} + other {# episodes} + }', + 'episode' => 'Episode', + 'visibility' => 'Visibility', + 'downloads' => 'Downloads', + 'comments' => 'Comments', + 'actions' => 'Actions', + ], + 'messages' => [ + 'createSuccess' => 'Episode has been successfully created!', + 'editSuccess' => 'Episode has been successfully updated!', + 'publishSuccess' => '{publication_status, select, + published {Episode successfully published!} + scheduled {Episode publication successfully scheduled!} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not published.} + }', + 'publishCancelSuccess' => 'Episode publication successfully cancelled!', + 'unpublishBeforeDeleteTip' => 'You must unpublish the episode before deleting it.', + 'scheduleDateError' => 'Schedule date must be set!', + 'deletePublishedEpisodeError' => 'Please unpublish the episode before deleting it.', + 'deleteSuccess' => 'Episode successfully deleted!', + 'deleteError' => 'Failed to delete episode {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deleteFileError' => 'Failed to delete {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + } file {file_key}. You may manually remove it from your disk.', + 'sameSlugError' => 'An episode with the chosen slug already exists.', + ], + 'form' => [ + 'file_size_error' => + 'Your file size is too big! Max size is {0}. Increase the `memory_limit`, `upload_max_filesize` and `post_max_size` values in your php configuration file then restart your web server to upload your file.', + 'audio_file' => 'Audio file', + 'audio_file_hint' => 'Choose an .mp3 or .m4a audio file.', + 'info_section_title' => 'Episode info', + 'cover' => 'Episode cover', + 'cover_hint' => + 'If you do not set a cover, the podcast cover will be used instead.', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'title' => 'Title', + 'title_hint' => + 'Should contain a clear and concise episode name. Do not specify the episode or season numbers here.', + 'permalink' => 'Permalink', + 'season_number' => 'Season', + 'episode_number' => 'Episode', + 'type' => [ + 'label' => 'Type', + 'full' => 'Full', + 'full_hint' => 'Complete content (the episode)', + 'trailer' => 'Trailer', + 'trailer_hint' => 'Short, promotional piece of content that represents a preview of the current show', + 'bonus' => 'Bonus', + 'bonus_hint' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show', + ], + 'premium_title' => 'Premium', + 'premium' => 'Episode must be accessible to premium subscribers only', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does the episode contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'show_notes_section_title' => 'Show notes', + 'show_notes_section_subtitle' => + 'Up to 4000 characters, be clear and concise. Show notes help potential listeners in finding the episode.', + 'description' => 'Description', + 'description_footer' => 'Description footer', + 'description_footer_hint' => + 'This text is added at the end of each episode description, it is a good place to input your social links for example.', + 'additional_files_section_title' => 'Additional files', + 'additional_files_section_subtitle' => + 'These files may be used by other platforms to provide better experience to your audience. See the {podcastNamespaceLink} for more information.', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this episode about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real or fictional location', + 'transcript' => 'Transcript (subtitles / closed captions)', + 'transcript_hint' => 'Only .srt or .vtt are allowed.', + 'transcript_download' => 'Download transcript', + 'transcript_file' => 'Transcript file (.srt or .vtt)', + 'transcript_remote_url' => 'Remote url for transcript', + 'transcript_file_delete' => 'Delete transcript file', + 'chapters' => 'Chapters', + 'chapters_hint' => 'File must be in JSON Chapters format.', + 'chapters_download' => 'Download chapters', + 'chapters_file' => 'Chapters file', + 'chapters_remote_url' => 'Remote url for chapters file', + 'chapters_file_delete' => 'Delete chapters file', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the episode', + 'custom_rss_hint' => 'This will be injected within the ❬item❭ tag.', + 'block' => 'Episode should be hidden from public catalogues', + 'block_hint' => + 'The episode show or hide status: toggling this on prevents the episode from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'submit_create' => 'Create episode', + 'submit_edit' => 'Save episode', + ], + 'publish_form' => [ + 'back_to_episode_dashboard' => 'Back to episode dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your episode. The message will be broadcasted to all your followers in the fediverse and be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + 'with_podcast' => 'Publish alongside podcast', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_clear' => 'Clear publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the episode release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit' => 'Publish', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your episode.', + 'message_warning_submit' => 'Publish anyways', + ], + 'publish_date_edit_form' => [ + 'new_publication_date' => 'New publication date', + 'new_publication_date_hint' => 'Must be set to a past date.', + 'submit' => 'Edit publication date', + ], + 'unpublish_form' => [ + 'disclaimer' => + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", + 'understand' => 'I understand, I want to unpublish the episode', + 'submit' => 'Unpublish', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.", + 'understand' => 'I understand, I want to delete the episode', + 'submit' => 'Delete', + ], + 'embed' => [ + 'title' => 'Embeddable player', + 'label' => + 'Pick a theme color, copy the embeddable player to clipboard, then paste it on your website.', + 'clipboard_iframe' => 'Copy embeddable player to clipboard', + 'clipboard_url' => 'Copy address to clipboard', + 'dark' => 'Dark', + 'dark-transparent' => 'Dark transparent', + 'light' => 'Light', + 'light-transparent' => 'Light transparent', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'text' => '{publication_status, select, + published {This episode is not yet published.} + scheduled {This episode is scheduled for publication on {publication_date}.} + with_podcast {This episode will be published at the same time as the podcast.} + other {This episode is not yet published.} + }', + 'preview' => 'Preview', + ], +]; diff --git a/modules/Admin/Language/zh-hant/EpisodeNavigation.php b/modules/Admin/Language/zh-hant/EpisodeNavigation.php new file mode 100644 index 00000000..1406e301 --- /dev/null +++ b/modules/Admin/Language/zh-hant/EpisodeNavigation.php @@ -0,0 +1,23 @@ + 'View episode page', + 'dashboard' => 'Episode dashboard', + 'episode-view' => 'Home', + 'episode-edit' => 'Edit episode', + 'episode-persons-manage' => 'Manage persons', + 'embed-add' => 'Embeddable player', + 'clips' => 'Clips', + 'video-clips-list' => 'Video clips', + 'video-clips-create' => 'New video clip', + 'soundbites-list' => 'Soundbites', + 'soundbites-create' => 'New soundbite', +]; diff --git a/modules/Admin/Language/zh-hant/Fediverse.php b/modules/Admin/Language/zh-hant/Fediverse.php new file mode 100644 index 00000000..0e4ca66d --- /dev/null +++ b/modules/Admin/Language/zh-hant/Fediverse.php @@ -0,0 +1,32 @@ + [ + 'actorNotFound' => 'The account could not be found!', + 'blockActorSuccess' => '{actor} has been blocked!', + 'unblockActorSuccess' => 'Actor has been unblocked!', + 'blockDomainSuccess' => '{domain} has been blocked!', + 'unblockDomainSuccess' => '{domain} has been unblocked!', + ], + 'blocked_actors' => 'Blocked accounts', + 'blocked_domains' => 'Blocked domains', + 'block_lists_form' => [ + 'handle' => 'Account handle', + 'handle_hint' => 'Input @username@domain account.', + 'domain' => 'Domain name', + 'submit' => 'Block!', + ], + 'list' => [ + 'actor' => 'Account', + 'domain' => 'Domain name', + 'unblock' => 'Unblock', + ], +]; diff --git a/modules/Admin/Language/zh-hant/Home.php b/modules/Admin/Language/zh-hant/Home.php new file mode 100644 index 00000000..3ff4c04d --- /dev/null +++ b/modules/Admin/Language/zh-hant/Home.php @@ -0,0 +1,14 @@ + 'All podcasts', + 'no_podcast' => 'No podcast found', +]; diff --git a/modules/Admin/Language/zh-hant/Install.php b/modules/Admin/Language/zh-hant/Install.php new file mode 100644 index 00000000..36e373a2 --- /dev/null +++ b/modules/Admin/Language/zh-hant/Install.php @@ -0,0 +1,61 @@ + 'Manual configuration', + 'manual_config_subtitle' => + 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'form' => [ + 'instance_config' => 'Instance configuration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', + 'db_hostname' => 'Database hostname', + 'db_name' => 'Database name', + 'db_username' => 'Database username', + 'db_password' => 'Database password', + 'db_prefix' => 'Database prefix', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Cache handler', + 'cacheHandlerOptions' => [ + 'file' => 'File', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Next', + 'submit' => 'Finish install', + 'create_superadmin' => 'Create your superadmin account', + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Your superadmin account has been created successfully. Login to start podcasting!', + 'databaseConnectError' => + 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'writeError' => + "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + ], +]; diff --git a/modules/Admin/Language/zh-hant/Navigation.php b/modules/Admin/Language/zh-hant/Navigation.php new file mode 100644 index 00000000..f3ffb129 --- /dev/null +++ b/modules/Admin/Language/zh-hant/Navigation.php @@ -0,0 +1,44 @@ + 'Toggle sidebar', + 'go_to_website' => 'Go to website', + 'go_to_admin' => 'Go to admin', + 'not-authorized' => 'Not authorized', + 'dashboard' => 'Dashboard', + 'admin' => 'Home', + 'podcasts' => 'Podcasts', + 'podcast-list' => 'All podcasts', + 'podcast-create' => 'New podcast', + 'all-podcast-imports' => 'All Podcast imports', + 'podcast-imports-add' => 'Import a podcast', + 'persons' => 'Persons', + 'person-list' => 'All persons', + 'person-create' => 'New person', + 'fediverse' => 'Fediverse', + 'fediverse-blocked-actors' => 'Blocked accounts', + 'fediverse-blocked-domains' => 'Blocked domains', + 'users' => 'Users', + 'user-list' => 'All users', + 'user-create' => 'New user', + 'pages' => 'Pages', + 'page-list' => 'All pages', + 'page-create' => 'New Page', + 'settings' => 'Settings', + 'settings-general' => 'General', + 'settings-theme' => 'Theme', + 'admin-about' => 'About', + 'account' => [ + 'my-account' => 'My account', + 'change-password' => 'Change password', + 'logout' => 'Logout', + ], +]; diff --git a/modules/Admin/Language/zh-hant/Notifications.php b/modules/Admin/Language/zh-hant/Notifications.php new file mode 100644 index 00000000..2b139d51 --- /dev/null +++ b/modules/Admin/Language/zh-hant/Notifications.php @@ -0,0 +1,19 @@ + 'Notifications', + 'reply' => '{actor_username} replied to your post', + 'favourite' => '{actor_username} favourited your post', + 'reblog' => '{actor_username} shared your post', + 'follow' => '{actor_username} started following you', + 'no_notifications' => 'No notifications', + 'mark_all_as_read' => 'Mark all as read', +]; diff --git a/modules/Admin/Language/zh-hant/Page.php b/modules/Admin/Language/zh-hant/Page.php new file mode 100644 index 00000000..b6f49de5 --- /dev/null +++ b/modules/Admin/Language/zh-hant/Page.php @@ -0,0 +1,30 @@ + 'Back to home', + 'page' => 'Page', + 'all_pages' => 'All pages', + 'create' => 'New page', + 'go_to_page' => 'Go to page', + 'edit' => 'Edit page', + 'delete' => 'Delete page', + 'form' => [ + 'title' => 'Title', + 'permalink' => 'Permalink', + 'content' => 'Content', + 'submit_create' => 'Create page', + 'submit_edit' => 'Save', + ], + 'messages' => [ + 'createSuccess' => 'The page “{pageTitle}” was created successfully!', + 'editSuccess' => 'The page was successfully updated!', + ], +]; diff --git a/modules/Admin/Language/zh-hant/Pager.php b/modules/Admin/Language/zh-hant/Pager.php new file mode 100644 index 00000000..e25ee638 --- /dev/null +++ b/modules/Admin/Language/zh-hant/Pager.php @@ -0,0 +1,21 @@ + 'Page navigation', + 'first' => 'First', + 'previous' => 'Previous', + 'next' => 'Next', + 'last' => 'Last', + 'older' => 'Older', + 'newer' => 'Newer', + 'invalidTemplate' => '{0} is not a valid Pager template.', + 'invalidPaginationGroup' => '{0} is not a valid Pagination group.', +]; diff --git a/modules/Admin/Language/zh-hant/Person.php b/modules/Admin/Language/zh-hant/Person.php new file mode 100644 index 00000000..a652be9f --- /dev/null +++ b/modules/Admin/Language/zh-hant/Person.php @@ -0,0 +1,65 @@ + 'Persons', + 'all_persons' => 'All persons', + 'no_person' => 'Nobody found!', + 'create' => 'Create a person', + 'view' => 'View person', + 'edit' => 'Edit person', + 'delete' => 'Delete person', + 'messages' => [ + 'createSuccess' => 'Person has been successfully created!', + 'editSuccess' => 'Person has been successfully updated!', + 'deleteSuccess' => 'Person has been removed!', + ], + 'form' => [ + 'avatar' => 'Avatar', + 'avatar_size_hint' => + 'Avatar must be squared and at least 400px wide and tall.', + 'full_name' => 'Full name', + 'full_name_hint' => 'This is the full name or alias of the person.', + 'unique_name' => 'Unique name', + 'unique_name_hint' => 'Used for URLs', + 'information_url' => 'Information URL', + 'information_url_hint' => + 'Url to a relevant resource of information about the person, such as a homepage or third-party profile platform.', + 'submit_create' => 'Create person', + 'submit_edit' => 'Save person', + ], + 'podcast_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this podcast', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'episode_form' => [ + 'title' => 'Manage persons', + 'add_section_title' => 'Add persons to this episode', + 'add_section_subtitle' => 'You may pick several persons and roles.', + 'persons' => 'Persons', + 'persons_hint' => + 'You may select one or several persons with the same roles. You need to create the persons first.', + 'roles' => 'Roles', + 'roles_hint' => + 'You may select none, one or several roles for a person.', + 'submit_add' => 'Add person(s)', + 'remove' => 'Remove', + ], + 'credits' => 'Credits', +]; diff --git a/modules/Admin/Language/zh-hant/Platforms.php b/modules/Admin/Language/zh-hant/Platforms.php new file mode 100644 index 00000000..e161181c --- /dev/null +++ b/modules/Admin/Language/zh-hant/Platforms.php @@ -0,0 +1,43 @@ + [ + 'podcasting' => 'Podcasting platforms', + 'social' => 'Social networks', + 'funding' => 'Funding links', + ], + 'website' => 'Website', + 'home_url' => 'Go to {platformName} website', + 'register' => 'Register', + 'submit_url' => 'Submit your podcast on {platformName}', + 'your_link' => 'Your link', + 'your_id' => [ + 'podcasting' => 'Your ID', + 'social' => 'Your ID', + 'funding' => 'Your CTA', + ], + 'your_cta' => 'Your call to action', + 'visible' => 'Display in podcast homepage?', + 'on_embed' => 'Display on embeddable player?', + 'remove' => 'Remove {platformName}', + 'submit' => 'Save', + 'messages' => [ + 'updateSuccess' => 'Platform links have been successfully updated!', + 'removeLinkSuccess' => 'The platform link has been removed.', + 'removeLinkError' => + 'The platform link could not be removed. Try again.', + ], + 'description' => [ + 'podcasting' => 'The podcast ID on this platform', + 'social' => 'The podcast account ID on this platform', + 'funding' => 'Call to action message', + ], +]; diff --git a/modules/Admin/Language/zh-hant/Podcast.php b/modules/Admin/Language/zh-hant/Podcast.php new file mode 100644 index 00000000..ff0daebc --- /dev/null +++ b/modules/Admin/Language/zh-hant/Podcast.php @@ -0,0 +1,330 @@ + 'All podcasts', + 'no_podcast' => 'No podcast found!', + 'create' => 'Create podcast', + 'import' => 'Import podcast', + 'all_imports' => 'Podcast imports', + 'new_episode' => 'New Episode', + 'view' => 'View podcast', + 'edit' => 'Edit podcast', + 'publish' => 'Publish podcast', + 'publish_edit' => 'Edit publication', + 'delete' => 'Delete podcast', + 'see_episodes' => 'See episodes', + 'see_contributors' => 'See contributors', + 'monetization_other' => 'Other monetization', + 'go_to_page' => 'Go to page', + 'latest_episodes' => 'Latest episodes', + 'see_all_episodes' => 'See all episodes', + 'draft' => 'Draft', + 'messages' => [ + 'createSuccess' => 'Podcast successfully created!', + 'editSuccess' => 'Podcast has been successfully updated!', + 'importSuccess' => 'Podcast has been successfully imported!', + 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', + 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, + cover {cover} + banner {banner} + other {media} + }.', + 'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.', + 'podcastFeedUpdateSuccess' => 'Successful update: {number_of_new_episodes, plural, + one {# episode was} + other {# episodes were} + } added to the podcast!', + 'podcastFeedUpToDate' => 'Podcast is already up to date.', + 'publishError' => 'This podcast is either already published or scheduled for publication.', + 'publishEditError' => 'This podcast is not scheduled for publication.', + 'publishCancelSuccess' => 'Podcast publication successfully cancelled!', + 'scheduleDateError' => 'Schedule date must be set!', + ], + 'form' => [ + 'identity_section_title' => 'Podcast identity', + 'identity_section_subtitle' => 'These fields allow you to get noticed.', + 'fediverse_section_title' => 'Fediverse identity', + + 'cover' => 'Podcast cover', + 'cover_size_hint' => 'Cover must be squared and at least 1400px wide and tall.', + 'banner' => 'Podcast banner', + 'banner_size_hint' => 'Banner must have a 3:1 ratio and be at least 1500px wide.', + 'banner_delete' => 'Delete podcast banner', + 'title' => 'Title', + 'handle' => 'Handle', + 'handle_hint' => + 'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.', + 'type' => [ + 'label' => 'Type', + 'episodic' => 'Episodic', + 'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.', + 'serial' => 'Serial', + 'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.', + ], + 'medium' => [ + 'label' => 'Medium', + 'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.', + 'podcast' => 'Podcast', + 'podcast_hint' => 'Describes a feed for a podcast show.', + 'music' => 'Music', + 'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.', + 'audiobook' => 'Audiobook', + 'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.', + ], + 'description' => 'Description', + 'classification_section_title' => 'Classification', + 'classification_section_subtitle' => + 'These fields will impact your audience and competition.', + 'language' => 'Language', + 'category' => 'Category', + 'category_placeholder' => 'Select a category…', + 'other_categories' => 'Other categories', + 'parental_advisory' => [ + 'label' => 'Parental advisory', + 'hint' => 'Does it contain explicit content?', + 'undefined' => 'undefined', + 'clean' => 'Clean', + 'explicit' => 'Explicit', + ], + 'author_section_title' => 'Author', + 'author_section_subtitle' => 'Who is managing the podcast?', + 'owner_name' => 'Owner name', + 'owner_name_hint' => + 'For administrative use only. Visible in the public RSS feed.', + 'owner_email' => 'Owner email', + 'owner_email_hint' => + 'Will be used by most platforms to verify the podcast ownership. Visible in the public RSS feed.', + 'is_owner_email_removed_from_feed' => 'Remove the owner email from the public RSS feed', + 'is_owner_email_removed_from_feed_hint' => 'You may need to temporarily unhide the email so that a directory can verify your podcast ownership.', + 'publisher' => 'Publisher', + 'publisher_hint' => + 'The group responsible for creating the show. Often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.', + 'copyright' => 'Copyright', + 'location_section_title' => 'Location', + 'location_section_subtitle' => 'What place is this podcast about?', + 'location_name' => 'Location name or address', + 'location_name_hint' => 'This can be a real place or fictional', + 'monetization_section_title' => 'Monetization', + 'monetization_section_subtitle' => + 'Earn money thanks to your audience.', + 'premium' => 'Premium', + 'premium_by_default' => 'Episodes must be set as premium by default', + 'premium_by_default_hint' => 'Podcast episodes will be marked as premium by default. You can still choose to set some episodes, trailers or bonuses as public.', + 'op3' => 'Open Podcast Prefix Project (OP3)', + 'op3_link' => 'Visit your OP3 dashboard (external link)', + 'op3_hint' => 'Value your analytics data with OP3, an open-source and trusted third party analytics service. Share, validate and compare your analytics data with the open podcasting ecosystem.', + 'op3_enable' => 'Enable OP3 analytics service', + 'op3_enable_hint' => 'For security reasons, premium episodes\' analytics data will not be shared with OP3.', + 'payment_pointer' => 'Payment Pointer for Web Monetization', + 'payment_pointer_hint' => + 'This is your where you will receive money thanks to Web Monetization', + 'advanced_section_title' => 'Advanced Parameters', + 'advanced_section_subtitle' => + 'If you need RSS tags that Castopod does not handle, set them here.', + 'custom_rss' => 'Custom RSS tags for the podcast', + 'custom_rss_hint' => 'This will be injected within the ❬channel❭ tag.', + 'verify_txt' => 'Ownership verification TXT', + 'verify_txt_hint' => 'Rather than relying on email, certain third-party services may confirm your podcast ownership by requesting you to embed a verification text within your feed.', + 'verify_txt_helper' => 'This text is injected into a tag.', + 'new_feed_url' => 'New feed URL', + 'new_feed_url_hint' => 'Use this field when you move to another domain or podcast hosting platform. By default, the value is set to the current RSS URL if the podcast is imported.', + 'old_feed_url' => 'Old feed URL', + 'partnership' => 'Partnership', + 'partner_id' => 'ID', + 'partner_link_url' => 'Link URL', + 'partner_image_url' => 'Image URL', + 'partner_id_hint' => 'Your own partner ID', + 'partner_link_url_hint' => 'The generic partner link address', + 'partner_image_url_hint' => 'The generic partner image address', + 'block' => 'Podcast should be hidden from public catalogues', + 'block_hint' => + 'The podcast show or hide status: toggling this on prevents the entire podcast from appearing in Apple Podcasts, Google Podcasts, and any third party apps that pull shows from these directories. (Not guaranteed)', + 'complete' => 'Podcast will not be having new episodes', + 'lock' => 'Prevent podcast from being copied', + 'lock_hint' => + 'The purpose is to tell other podcast platforms whether they are allowed to import this feed. A value of yes means that any attempt to import this feed into a new platform should be rejected.', + 'submit_create' => 'Create podcast', + 'submit_edit' => 'Save podcast', + ], + 'category_options' => [ + 'uncategorized' => 'uncategorized', + 'arts' => 'Arts', + 'business' => 'Business', + 'comedy' => 'Comedy', + 'education' => 'Education', + 'fiction' => 'Fiction', + 'government' => 'Government', + 'health_and_fitness' => 'Health & Fitness', + 'history' => 'History', + 'kids_and_family' => 'Kids & Family', + 'leisure' => 'Leisure', + 'music' => 'Music', + 'news' => 'News', + 'religion_and_spirituality' => 'Religion & Spirituality', + 'science' => 'Science', + 'society_and_culture' => 'Society & Culture', + 'sports' => 'Sports', + 'technology' => 'Technology', + 'true_crime' => 'True Crime', + 'tv_and_film' => 'TV & Film', + 'books' => 'Books', + 'design' => 'Design', + 'fashion_and_beauty' => 'Fashion & Beauty', + 'food' => 'Food', + 'performing_arts' => 'Performing Arts', + 'visual_arts' => 'Visual Arts', + 'careers' => 'Careers', + 'entrepreneurship' => 'Entrepreneurship', + 'investing' => 'Investing', + 'management' => 'Management', + 'marketing' => 'Marketing', + 'non_profit' => 'Non-Profit', + 'comedy_interviews' => 'Comedy Interviews', + 'improv' => 'Improv', + 'stand_up' => 'Stand-Up', + 'courses' => 'Courses', + 'how_to' => 'How To', + 'language_learning' => 'Language Learning', + 'self_improvement' => 'Self-Improvement', + 'comedy_fiction' => 'Comedy Fiction', + 'drama' => 'Drama', + 'science_fiction' => 'Science Fiction', + 'alternative_health' => 'Alternative Health', + 'fitness' => 'Fitness', + 'medicine' => 'Medicine', + 'mental_health' => 'Mental Health', + 'nutrition' => 'Nutrition', + 'sexuality' => 'Sexuality', + 'education_for_kids' => 'Education for Kids', + 'parenting' => 'Parenting', + 'pets_and_animals' => 'Pets & Animals', + 'stories_for_kids' => 'Stories for Kids', + 'animation_and_manga' => 'Animation & Manga', + 'automotive' => 'Automotive', + 'aviation' => 'Aviation', + 'crafts' => 'Crafts', + 'games' => 'Games', + 'hobbies' => 'Hobbies', + 'home_and_garden' => 'Home & Garden', + 'video_games' => 'Video Games', + 'music_commentary' => 'Music Commentary', + 'music_history' => 'Music History', + 'music_interviews' => 'Music Interviews', + 'business_news' => 'Business News', + 'daily_news' => 'Daily News', + 'entertainment_news' => 'Entertainment News', + 'news_commentary' => 'News Commentary', + 'politics' => 'Politics', + 'sports_news' => 'Sports News', + 'tech_news' => 'Tech News', + 'buddhism' => 'Buddhism', + 'christianity' => 'Christianity', + 'hinduism' => 'Hinduism', + 'islam' => 'Islam', + 'judaism' => 'Judaism', + 'religion' => 'Religion', + 'spirituality' => 'Spirituality', + 'astronomy' => 'Astronomy', + 'chemistry' => 'Chemistry', + 'earth_sciences' => 'Earth Sciences', + 'life_sciences' => 'Life Sciences', + 'mathematics' => 'Mathematics', + 'natural_sciences' => 'Natural Sciences', + 'nature' => 'Nature', + 'physics' => 'Physics', + 'social_sciences' => 'Social Sciences', + 'documentary' => 'Documentary', + 'personal_journals' => 'Personal Journals', + 'philosophy' => 'Philosophy', + 'places_and_travel' => 'Places & Travel', + 'relationships' => 'Relationships', + 'baseball' => 'Baseball', + 'basketball' => 'Basketball', + 'cricket' => 'Cricket', + 'fantasy_sports' => 'Fantasy Sports', + 'football' => 'Football', + 'golf' => 'Golf', + 'hockey' => 'Hockey', + 'rugby' => 'Rugby', + 'running' => 'Running', + 'soccer' => 'Soccer', + 'swimming' => 'Swimming', + 'tennis' => 'Tennis', + 'volleyball' => 'Volleyball', + 'wilderness' => 'Wilderness', + 'wrestling' => 'Wrestling', + 'after_shows' => 'After Shows', + 'film_history' => 'Film History', + 'film_interviews' => 'Film Interviews', + 'film_reviews' => 'Film Reviews', + 'tv_reviews' => 'TV Reviews', + ], + 'publish_form' => [ + 'back_to_podcast_dashboard' => 'Back to podcast dashboard', + 'post' => 'Your announcement post', + 'post_hint' => + "Write a message to announce the publication of your podcast. The message will be featured in your podcast's homepage.", + 'message_placeholder' => 'Write your message…', + 'submit' => 'Publish', + 'publication_date' => 'Publication date', + 'publication_method' => [ + 'now' => 'Now', + 'schedule' => 'Schedule', + ], + 'scheduled_publication_date' => 'Scheduled publication date', + 'scheduled_publication_date_hint' => + 'You can schedule the podcast release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm', + 'submit_edit' => 'Edit publication', + 'cancel_publication' => 'Cancel publication', + 'message_warning' => 'You did not write a message for your announcement post!', + 'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your podcast.', + 'message_warning_submit' => 'Publish anyway', + ], + 'publication_status_banner' => [ + 'draft_mode' => 'draft mode', + 'not_published' => 'This podcast is not yet published.', + 'scheduled' => 'This podcast is scheduled for publication on {publication_date}.', + ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", + 'understand' => 'I understand, I want the podcast to be permanently deleted', + 'submit' => 'Delete', + ], + 'by' => 'By {publisher}', + 'season' => 'Season {seasonNumber}', + 'list_of_episodes_year' => '{year} episodes ({episodeCount})', + 'list_of_episodes_season' => + 'Season {seasonNumber} episodes ({episodeCount})', + 'no_episode' => 'No episode found!', + 'follow' => 'Follow', + 'followers' => '{numberOfFollowers, plural, + one {# follower} + other {# followers} + }', + 'posts' => '{numberOfPosts, plural, + one {# post} + other {# posts} + }', + 'activity' => 'Activity', + 'episodes' => 'Episodes', + 'sponsor' => 'Sponsor', + 'funding_links' => 'Funding links for {podcastTitle}', + 'find_on' => 'Find {podcastTitle} on', + 'listen_on' => 'Listen on', +]; diff --git a/modules/Admin/Language/zh-hant/PodcastNavigation.php b/modules/Admin/Language/zh-hant/PodcastNavigation.php new file mode 100644 index 00000000..bb777707 --- /dev/null +++ b/modules/Admin/Language/zh-hant/PodcastNavigation.php @@ -0,0 +1,42 @@ + 'Go to podcast page', + 'rss_feed' => 'RSS feed', + 'dashboard' => 'Podcast dashboard', + 'podcast-view' => 'Home', + 'podcast-edit' => 'Edit podcast', + 'podcast-persons-manage' => 'Manage persons', + 'podcast-imports' => 'Podcast imports', + 'podcast-imports-sync' => 'Sync feeds', + 'episodes' => 'Episodes', + 'episode-list' => 'All episodes', + 'episode-create' => 'New episode', + 'analytics' => 'Analytics', + 'podcast-analytics' => 'Audience overview', + 'podcast-analytics-webpages' => 'Web pages visits', + 'podcast-analytics-locations' => 'Locations', + 'podcast-analytics-unique-listeners' => 'Unique listeners', + 'podcast-analytics-players' => 'Players', + 'podcast-analytics-listening-time' => 'Listening time', + 'podcast-analytics-time-periods' => 'Time periods', + 'monetization' => 'Monetization', + 'subscription-list' => 'All subscriptions', + 'subscription-create' => 'Add subscription', + 'contributors' => 'Contributors', + 'contributor-list' => 'All contributors', + 'contributor-add' => 'Add contributor', + 'broadcast' => 'Broadcast', + 'platforms-podcasting' => 'Podcasting apps', + 'platforms-social' => 'Social networks', + 'platforms-funding' => 'Funding links', + 'podcast-monetization-other' => 'Other', +]; diff --git a/modules/Admin/Language/zh-hant/Settings.php b/modules/Admin/Language/zh-hant/Settings.php new file mode 100644 index 00000000..4a70dcba --- /dev/null +++ b/modules/Admin/Language/zh-hant/Settings.php @@ -0,0 +1,58 @@ + 'General settings', + 'instance' => [ + 'title' => 'Instance', + 'site_icon' => 'Site icon', + 'site_icon_delete' => 'Delete site icon', + 'site_icon_hint' => 'Site icons are what you see on your browser tabs, bookmarks bar, and when you add a website as a shortcut on mobile devices.', + 'site_icon_helper' => 'Icon must be squared and at least 512px wide and tall.', + 'site_name' => 'Site name', + 'site_description' => 'Site description', + 'submit' => 'Save', + 'editSuccess' => 'Instance has been updated successfully!', + 'deleteIconSuccess' => 'Site icon has been remove successfully!', + ], + 'images' => [ + 'title' => 'Images', + 'subtitle' => 'Here you can regenerate all images based on the originals that were uploaded. To be used if you find that some images are missing. This task may take a while.', + 'regenerate' => 'Regenerate images', + 'regenerationSuccess' => 'All images have been regenerated successfully!', + ], + 'housekeeping' => [ + 'title' => 'Housekeeping', + 'subtitle' => 'Runs various housekeeping tasks. Use this feature if you ever encounter issues with media files or data integrity. These tasks may take a while.', + 'reset_counts' => 'Reset counts', + 'reset_counts_helper' => 'This option will recalculate and reset all data counts (number of followers, posts, comments, …).', + 'rewrite_media' => 'Rewrite media metadata', + 'rewrite_media_helper' => 'This option will delete all superfluous media files and recreate them (images, audio files, transcripts, chapters, …)', + 'rename_episodes_files' => 'Rename episode audio files', + 'rename_episodes_files_hint' => 'This option will rename all episodes audio files to a random string of characters. Use this if one of your private episodes link was leaked as this will effectively hide it.', + 'clear_cache' => 'Clear all cache', + 'clear_cache_helper' => 'This option will flush redis cache or writable/cache files.', + 'run' => 'Run housekeeping', + 'runSuccess' => 'Housekeeping has been run successfully!', + ], + 'theme' => [ + 'title' => 'Theme', + 'accent_section_title' => 'Accent color', + 'accent_section_subtitle' => 'Choose the color to determine the look and feel of all public pages.', + 'pine' => 'Pine', + 'crimson' => 'Crimson', + 'amber' => 'Amber', + 'lake' => 'Lake', + 'jacaranda' => 'Jacaranda', + 'onyx' => 'Onyx', + 'submit' => 'Save', + 'setInstanceThemeSuccess' => 'Theme has been updated successfully!', + ], +]; diff --git a/modules/Admin/Language/zh-hant/Soundbite.php b/modules/Admin/Language/zh-hant/Soundbite.php new file mode 100644 index 00000000..a3f828fe --- /dev/null +++ b/modules/Admin/Language/zh-hant/Soundbite.php @@ -0,0 +1,31 @@ + [ + 'title' => 'Soundbites', + 'soundbite' => 'Soundbite', + ], + 'messages' => [ + 'createSuccess' => 'Soundbite has been successfully created!', + 'deleteSuccess' => 'Soundbite has been successfully removed!', + ], + 'form' => [ + 'title' => 'New soundbite', + 'soundbite_title' => 'Soundbite title', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'submit' => 'Create soundbite', + ], + 'play' => 'Play soundbite', + 'stop' => 'Stop soundbite', + 'create' => 'New soundbite', + 'delete' => 'Delete soundbite', +]; diff --git a/modules/Admin/Language/zh-hant/Validation.php b/modules/Admin/Language/zh-hant/Validation.php new file mode 100644 index 00000000..f76c3163 --- /dev/null +++ b/modules/Admin/Language/zh-hant/Validation.php @@ -0,0 +1,17 @@ + + '{field} is either not an image, or it is not wide or tall enough.', + 'is_image_ratio' => + '{field} is either not an image or not of the right ratio.', + 'is_json' => '{field} contains invalid JSON.', +]; diff --git a/modules/Admin/Language/zh-hant/VideoClip.php b/modules/Admin/Language/zh-hant/VideoClip.php new file mode 100644 index 00000000..638de697 --- /dev/null +++ b/modules/Admin/Language/zh-hant/VideoClip.php @@ -0,0 +1,72 @@ + [ + 'title' => 'Video clips', + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Clip is waiting to be processed.', + 'pending' => 'pending', + 'pending_hint' => 'Clip will be generated shortly.', + 'running' => 'running', + 'running_hint' => 'Clip is being generated.', + 'failed' => 'failed', + 'failed_hint' => 'Clip could not be generated: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Clip was generated successfully!', + ], + 'clip' => 'Clip', + 'duration' => 'Job duration', + ], + 'title' => 'Video clip: {videoClipLabel}', + 'download_clip' => 'Download clip', + 'create' => 'New video clip', + 'go_to_page' => 'Go to clip page', + 'retry' => 'Retry clip generation', + 'delete' => 'Delete clip', + 'logs' => 'Job logs', + 'messages' => [ + 'alreadyExistingError' => 'The video clip you are trying to create already exists!', + 'addToQueueSuccess' => 'Video clip has been added to queue, awaiting to be created!', + 'deleteSuccess' => 'Video clip has been successfully removed!', + ], + 'format' => [ + 'landscape' => 'Landscape', + 'portrait' => 'Portrait', + 'squared' => 'Squared', + ], + 'form' => [ + 'title' => 'New video clip', + 'params_section_title' => 'Video clip parameters', + 'clip_title' => 'Clip title', + 'format' => [ + 'label' => 'Choose a format', + 'landscape_hint' => 'With a 16:9 ratio, landscape videos are great for PeerTube, Youtube and Vimeo.', + 'portrait_hint' => 'With a 9:16 ratio, portrait videos are great for TikTok, Youtube shorts and Instagram stories.', + 'squared_hint' => 'With a 1:1 ratio, squared videos are great for Mastodon, Facebook, Twitter and LinkedIn.', + ], + 'theme' => 'Select a theme', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'trim_start' => 'Trim start', + 'trim_end' => 'Trim end', + 'submit' => 'Create video clip', + ], + 'requirements' => [ + 'title' => 'Missing requirements', + 'missing' => 'You have missing requirements. Make sure to add all the required items to be allowed creating a video for this episode!', + 'ffmpeg' => 'FFmpeg', + 'gd' => 'Graphics Draw (GD)', + 'freetype' => 'Freetype library for GD', + 'transcript' => 'Transcript file (.srt)', + ], +]; diff --git a/modules/Analytics/AnalyticsTrait.php b/modules/Analytics/AnalyticsTrait.php index 7d1680b4..a345c597 100644 --- a/modules/Analytics/AnalyticsTrait.php +++ b/modules/Analytics/AnalyticsTrait.php @@ -10,12 +10,15 @@ declare(strict_types=1); namespace Modules\Analytics; -use Config\Services; - trait AnalyticsTrait { protected function registerPodcastWebpageHit(int $podcastId): void { + // Prevent analytics hit when authenticated + if (auth()->loggedIn()) { + return; + } + helper('analytics'); set_user_session_deny_list_ip(); @@ -23,18 +26,15 @@ trait AnalyticsTrait set_user_session_referer(); set_user_session_entry_page(); - $session = Services::session(); - $session->start(); + $session = service('session'); if (! $session->get('denyListIp')) { $db = db_connect(); $referer = $session->get('referer'); $domain = - parse_url($referer, PHP_URL_HOST) === null - ? '- Direct -' - : parse_url($referer, PHP_URL_HOST); - parse_str((string) parse_url($referer, PHP_URL_QUERY), $queries); + parse_url((string) $referer, PHP_URL_HOST) ?? '- Direct -'; + parse_str((string) parse_url((string) $referer, PHP_URL_QUERY), $queries); $keywords = $queries['q'] ?? null; $procedureName = $db->prefixTable('analytics_website'); diff --git a/modules/Analytics/Config/Analytics.php b/modules/Analytics/Config/Analytics.php index ab263a08..fb86c03b 100644 --- a/modules/Analytics/Config/Analytics.php +++ b/modules/Analytics/Config/Analytics.php @@ -20,9 +20,9 @@ class Analytics extends BaseConfig * @var array */ public array $routeFilters = [ - 'analytics-full-data' => 'permission:podcasts-view,podcast-view', - 'analytics-data' => 'permission:podcasts-view,podcast-view', - 'analytics-filtered-data' => 'permission:podcasts-view,podcast-view', + 'analytics-full-data' => 'permission:podcast$1.view', + 'analytics-data' => 'permission:podcast$1.view', + 'analytics-filtered-data' => 'permission:podcast$1.view', ]; /** @@ -37,16 +37,4 @@ class Analytics extends BaseConfig * Z&|qECKBrwgaaD>~;U/tXG1U%tSe_oi5Tzy)h>}5NC2npSrjvM0w_Q>cs=0o=H]* */ public string $salt = ''; - - /** - * get the full audio file url - * - * @param string|string[] $audioPath - */ - public function getAudioUrl(string | array $audioPath): string - { - helper('media'); - - return media_base_url($audioPath); - } } diff --git a/modules/Analytics/Config/Routes.php b/modules/Analytics/Config/Routes.php index 5285fbfd..9097151c 100644 --- a/modules/Analytics/Config/Routes.php +++ b/modules/Analytics/Config/Routes.php @@ -8,7 +8,7 @@ declare(strict_types=1); * @link https://castopod.org/ */ -$routes = service('routes'); +/** @var \CodeIgniter\Router\RouteCollection $routes */ /** * Analytics routes file @@ -27,14 +27,14 @@ $routes->group('', [ ], static function ($routes): void { $routes->group(config('Analytics')->gateway . '/(:num)/(:class)', static function ($routes): void { $routes->get('/', 'AnalyticsController::getData/$1/$2', [ - 'as' => 'analytics-full-data', + 'as' => 'analytics-full-data', 'filter' => config('Analytics') ->routeFilters[ 'analytics-full-data' ], ]); $routes->get('(:filter)', 'AnalyticsController::getData/$1/$2/$3', [ - 'as' => 'analytics-data', + 'as' => 'analytics-data', 'filter' => config('Analytics') ->routeFilters['analytics-data'], ]); @@ -42,7 +42,7 @@ $routes->group('', [ '(:filter)/(:num)', 'AnalyticsController::getData/$1/$2/$3/$4', [ - 'as' => 'analytics-filtered-data', + 'as' => 'analytics-filtered-data', 'filter' => config('Analytics') ->routeFilters[ 'analytics-filtered-data' @@ -53,23 +53,14 @@ $routes->group('', [ $routes->get(config('Analytics')->gateway . '/(:class)/(:filter)', 'AnalyticsController::getData/$1/$2', [ 'as' => 'analytics-data-instance', ]); - // Route for podcast audio file analytics (/audio/pack(podcast_id,episode_id,bytes_threshold,filesize,duration,date)/podcast_folder/filename.mp3) - $routes->head( - 'audio/(:base64)/(:any)', - 'EpisodeAnalyticsController::hit/$1/$2', - [ - 'as' => 'episode-analytics-hit', - ], - ); - $routes->get( - 'audio/(:base64)/(:any)', - 'EpisodeAnalyticsController::hit/$1/$2', - [ - 'as' => 'episode-analytics-hit', - ], - ); + + /** + * @deprecated Route for podcast audio file analytics (/audio/pack(podcast_id,episode_id,bytes_threshold,filesize,duration,date)/podcast_folder/filename.mp3) + */ + $routes->head('audio/(:base64)/(:any)', 'EpisodeAnalyticsController::hit/$1/$2'); + $routes->get('audio/(:base64)/(:any)', 'EpisodeAnalyticsController::hit/$1/$2'); }); // Show the Unknown UserAgents $routes->get('.well-known/unknown-useragents', 'UnknownUserAgentsController'); -$routes->get('.well-known/unknown-useragents/(:num)', 'UnknownUserAgentsController/$1'); +$routes->get('.well-known/unknown-useragents/(:num)', 'UnknownUserAgentsController::index/$1'); diff --git a/modules/Analytics/Controllers/AnalyticsController.php b/modules/Analytics/Controllers/AnalyticsController.php index fb0a0315..adf7e3e0 100644 --- a/modules/Analytics/Controllers/AnalyticsController.php +++ b/modules/Analytics/Controllers/AnalyticsController.php @@ -31,11 +31,13 @@ class AnalyticsController extends Controller } if (! is_numeric($params[0])) { + // @phpstan-ignore-next-line $this->analyticsModel = model('Analytics' . $params[0] . 'Model'); $this->methodName = 'getData' . $params[1]; return $this->{$method}(); } + // @phpstan-ignore-next-line $this->analyticsModel = model('Analytics' . $params[1] . 'Model'); $this->methodName = 'getData' . (count($params) >= 3 ? $params[2] : ''); diff --git a/modules/Analytics/Controllers/EpisodeAnalyticsController.php b/modules/Analytics/Controllers/EpisodeAnalyticsController.php index e1244d51..dfaff0a9 100644 --- a/modules/Analytics/Controllers/EpisodeAnalyticsController.php +++ b/modules/Analytics/Controllers/EpisodeAnalyticsController.php @@ -15,107 +15,32 @@ use App\Models\EpisodeModel; use CodeIgniter\Controller; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; -use CodeIgniter\HTTP\RequestInterface; -use CodeIgniter\HTTP\ResponseInterface; -use Config\Services; -use Modules\Analytics\Config\Analytics; -use Modules\PremiumPodcasts\Models\SubscriptionModel; -use Psr\Log\LoggerInterface; +use Deprecated; class EpisodeAnalyticsController extends Controller { - /** - * An array of helpers to be loaded automatically upon class instantiation. These helpers will be available to all - * other controllers that extend Analytics. - * - * @var string[] - */ - protected $helpers = ['analytics']; - - protected Analytics $config; - - /** - * Constructor. - */ - public function initController( - RequestInterface $request, - ResponseInterface $response, - LoggerInterface $logger - ): void { - // Do Not Edit This Line - parent::initController($request, $response, $logger); - - set_user_session_deny_list_ip(); - set_user_session_location(); - set_user_session_player(); - - $this->config = config('Analytics'); - } - - public function hit(string $base64EpisodeData, string ...$audioPath): RedirectResponse|ResponseInterface + #[Deprecated(message: 'Replaced by EpisodeAudioController::index method')] + public function hit(string $base64EpisodeData, string ...$audioPath): RedirectResponse { - $session = Services::session(); - $session->start(); - - $serviceName = ''; - if ($this->request->getGet('_from')) { - $serviceName = $this->request->getGet('_from'); - } elseif ($session->get('embed_domain') !== null) { - $serviceName = $session->get('embed_domain'); - } elseif ($session->get('referer') !== null && $session->get('referer') !== '- Direct -') { - $serviceName = parse_url($session->get('referer'), PHP_URL_HOST); - } - $episodeData = unpack( 'IpodcastId/IepisodeId/IbytesThreshold/IfileSize/Iduration/IpublicationDate', base64_url_decode($base64EpisodeData), ); - if (! $episodeData) { + if ($episodeData === false) { throw PageNotFoundException::forPageNotFound(); } - // check if episode is premium? - $episode = (new EpisodeModel())->getEpisodeById($episodeData['episodeId']); + $episode = new EpisodeModel() + ->getEpisodeById($episodeData['episodeId']); if (! $episode instanceof Episode) { - return $this->response->setStatusCode(404); + throw PageNotFoundException::forPageNotFound(); } - $subscription = null; - - // check if podcast is already unlocked before any token validation - if ($episode->is_premium && ($subscription = service('premium_podcasts')->subscription( - $episode->podcast->handle - )) === null) { - // look for token as GET parameter - if (($token = $this->request->getGet('token')) === null) { - return $this->response->setStatusCode( - 401, - 'Episode is premium, you must provide a token to unlock it.' - ); - } - - // check if there's a valid subscription for the provided token - if (($subscription = (new SubscriptionModel())->validateSubscription( - $episode->podcast->handle, - $token - )) === null) { - return $this->response->setStatusCode(401, 'Invalid token!'); - } - } - - podcast_hit( - $episodeData['podcastId'], - $episodeData['episodeId'], - $episodeData['bytesThreshold'], - $episodeData['fileSize'], - $episodeData['duration'], - $episodeData['publicationDate'], - $serviceName, - $subscription !== null ? $subscription->id : null + return redirect()->route( + 'episode-audio', + [$episode->podcast->handle, $episode->slug, $episode->audio->file_extension], ); - - return redirect()->to($this->config->getAudioUrl($episode->audio->file_path)); } } diff --git a/modules/Analytics/Database/Migrations/2017-12-01-000000_add_analytics_podcasts.php b/modules/Analytics/Database/Migrations/2017-12-01-000000_add_analytics_podcasts.php index f921aa24..e95fb7a8 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-000000_add_analytics_podcasts.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-000000_add_analytics_podcasts.php @@ -12,15 +12,17 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddAnalyticsPodcasts extends Migration +class AddAnalyticsPodcasts extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'podcast_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'date' => [ @@ -30,22 +32,22 @@ class AddAnalyticsPodcasts extends Migration // a hit in analytics podcast increments this value when a podcast is listened to in a given date. // Here, the "cumulative listening time" on a podcast per day // cannot surpass 999,999,999,999.999 seconds (~277,777,777 hours) - should be enough. - 'type' => 'DECIMAL(15,3)', + 'type' => 'DECIMAL(15,3)', 'unsigned' => true, ], 'bandwidth' => [ - 'type' => 'BIGINT', + 'type' => 'BIGINT', 'unsigned' => true, ], 'unique_listeners' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'default' => 1, + 'default' => 1, ], 'hits' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'default' => 1, + 'default' => 1, ], ]); $this->forge->addPrimaryKey(['podcast_id', 'date']); @@ -56,6 +58,7 @@ class AddAnalyticsPodcasts extends Migration $this->forge->createTable('analytics_podcasts'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_podcasts'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-010000_add_analytics_podcasts_by_episode.php b/modules/Analytics/Database/Migrations/2017-12-01-010000_add_analytics_podcasts_by_episode.php index de03aab9..421d76ca 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-010000_add_analytics_podcasts_by_episode.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-010000_add_analytics_podcasts_by_episode.php @@ -12,33 +12,35 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddAnalyticsPodcastsByEpisode extends Migration +class AddAnalyticsPodcastsByEpisode extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'podcast_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'date' => [ 'type' => 'DATE', ], 'episode_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'age' => [ - 'type' => 'INT', - 'comment' => 'Days since episode publication date', + 'type' => 'INT', + 'comment' => 'Days since episode publication date', 'unsigned' => true, ], 'hits' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'default' => 1, + 'default' => 1, ], ]); $this->forge->addPrimaryKey(['podcast_id', 'date', 'episode_id']); @@ -49,6 +51,7 @@ class AddAnalyticsPodcastsByEpisode extends Migration $this->forge->createTable('analytics_podcasts_by_episode'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_podcasts_by_episode'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-020000_add_analytics_podcasts_by_hour.php b/modules/Analytics/Database/Migrations/2017-12-01-020000_add_analytics_podcasts_by_hour.php index d3edf0ce..3050d0af 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-020000_add_analytics_podcasts_by_hour.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-020000_add_analytics_podcasts_by_hour.php @@ -12,28 +12,30 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddAnalyticsPodcastsByHour extends Migration +class AddAnalyticsPodcastsByHour extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'podcast_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'date' => [ 'type' => 'DATE', ], 'hour' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'hits' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'default' => 1, + 'default' => 1, ], ]); $this->forge->addPrimaryKey(['podcast_id', 'date', 'hour']); @@ -44,6 +46,7 @@ class AddAnalyticsPodcastsByHour extends Migration $this->forge->createTable('analytics_podcasts_by_hour'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_podcasts_by_hour'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-030000_add_analytics_podcasts_by_player.php b/modules/Analytics/Database/Migrations/2017-12-01-030000_add_analytics_podcasts_by_player.php index e12481eb..e6b653c1 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-030000_add_analytics_podcasts_by_player.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-030000_add_analytics_podcasts_by_player.php @@ -12,45 +12,47 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddAnalyticsPodcastsByPlayer extends Migration +class AddAnalyticsPodcastsByPlayer extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'podcast_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'date' => [ 'type' => 'DATE', ], 'service' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 128, ], 'app' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 128, ], 'device' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 32, ], 'os' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 32, ], 'is_bot' => [ - 'type' => 'TINYINT', + 'type' => 'TINYINT', 'constraint' => 1, - 'default' => 0, + 'default' => 0, ], 'hits' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'default' => 1, + 'default' => 1, ], ]); $this->forge->addPrimaryKey(['podcast_id', 'date', 'service', 'app', 'device', 'os', 'is_bot']); @@ -61,6 +63,7 @@ class AddAnalyticsPodcastsByPlayer extends Migration $this->forge->createTable('analytics_podcasts_by_player'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_podcasts_by_player'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-040000_add_analytics_podcasts_by_country.php b/modules/Analytics/Database/Migrations/2017-12-01-040000_add_analytics_podcasts_by_country.php index 2290dd03..cd668c0b 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-040000_add_analytics_podcasts_by_country.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-040000_add_analytics_podcasts_by_country.php @@ -12,29 +12,31 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddAnalyticsPodcastsByCountry extends Migration +class AddAnalyticsPodcastsByCountry extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'podcast_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'date' => [ 'type' => 'DATE', ], 'country_code' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 3, - 'comment' => 'ISO 3166-1 code.', + 'comment' => 'ISO 3166-1 code.', ], 'hits' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'default' => 1, + 'default' => 1, ], ]); $this->forge->addPrimaryKey(['podcast_id', 'date', 'country_code']); @@ -45,6 +47,7 @@ class AddAnalyticsPodcastsByCountry extends Migration $this->forge->createTable('analytics_podcasts_by_country'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_podcasts_by_country'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-050000_add_analytics_podcasts_by_region.php b/modules/Analytics/Database/Migrations/2017-12-01-050000_add_analytics_podcasts_by_region.php index fd9a8bac..3e5ec698 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-050000_add_analytics_podcasts_by_region.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-050000_add_analytics_podcasts_by_region.php @@ -12,29 +12,31 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddAnalyticsPodcastsByRegion extends Migration +class AddAnalyticsPodcastsByRegion extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'podcast_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'date' => [ 'type' => 'DATE', ], 'country_code' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 3, - 'comment' => 'ISO 3166-1 code.', + 'comment' => 'ISO 3166-1 code.', ], 'region_code' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 3, - 'comment' => 'ISO 3166-2 code.', + 'comment' => 'ISO 3166-2 code.', ], 'latitude' => [ 'type' => 'DECIMAL(8,6)', @@ -45,9 +47,9 @@ class AddAnalyticsPodcastsByRegion extends Migration 'null' => true, ], 'hits' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'default' => 1, + 'default' => 1, ], ]); $this->forge->addPrimaryKey(['podcast_id', 'date', 'country_code', 'region_code']); @@ -58,6 +60,7 @@ class AddAnalyticsPodcastsByRegion extends Migration $this->forge->createTable('analytics_podcasts_by_region'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_podcasts_by_region'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-060000_add_analytics_website_by_browser.php b/modules/Analytics/Database/Migrations/2017-12-01-060000_add_analytics_website_by_browser.php index 4f7ecbc7..8e37d154 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-060000_add_analytics_website_by_browser.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-060000_add_analytics_website_by_browser.php @@ -12,28 +12,30 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddAnalyticsWebsiteByBrowser extends Migration +class AddAnalyticsWebsiteByBrowser extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'podcast_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'date' => [ 'type' => 'DATE', ], 'browser' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 128, ], 'hits' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'default' => 1, + 'default' => 1, ], ]); @@ -45,6 +47,7 @@ class AddAnalyticsWebsiteByBrowser extends Migration $this->forge->createTable('analytics_website_by_browser'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_website_by_browser'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-070000_add_analytics_website_by_referer.php b/modules/Analytics/Database/Migrations/2017-12-01-070000_add_analytics_website_by_referer.php index 6b6b4db6..cfbbd362 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-070000_add_analytics_website_by_referer.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-070000_add_analytics_website_by_referer.php @@ -12,39 +12,41 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddAnalyticsWebsiteByReferer extends Migration +class AddAnalyticsWebsiteByReferer extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'podcast_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'date' => [ 'type' => 'DATE', ], 'referer_url' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 512, ], 'domain' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 128, - 'null' => true, + 'null' => true, ], 'keywords' => [ 'type' => 'VARCHAR', // length of referer_url (512) - domain (128) = 384 'constraint' => 384, - 'null' => true, + 'null' => true, ], 'hits' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'default' => 1, + 'default' => 1, ], ]); $this->forge->addPrimaryKey(['podcast_id', 'date', 'referer_url']); @@ -55,6 +57,7 @@ class AddAnalyticsWebsiteByReferer extends Migration $this->forge->createTable('analytics_website_by_referer'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_website_by_referer'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-080000_add_analytics_website_by_entry_page.php b/modules/Analytics/Database/Migrations/2017-12-01-080000_add_analytics_website_by_entry_page.php index b1576689..bdfebcbc 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-080000_add_analytics_website_by_entry_page.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-080000_add_analytics_website_by_entry_page.php @@ -12,28 +12,30 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddAnalyticsWebsiteByEntryPage extends Migration +class AddAnalyticsWebsiteByEntryPage extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'podcast_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'date' => [ 'type' => 'DATE', ], 'entry_page_url' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 512, ], 'hits' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'default' => 1, + 'default' => 1, ], ]); $this->forge->addPrimaryKey(['podcast_id', 'date', 'entry_page_url']); @@ -44,6 +46,7 @@ class AddAnalyticsWebsiteByEntryPage extends Migration $this->forge->createTable('analytics_website_by_entry_page'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_website_by_entry_page'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-090000_add_analytics_unknown_useragents.php b/modules/Analytics/Database/Migrations/2017-12-01-090000_add_analytics_unknown_useragents.php index ab1f1eb9..986848c5 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-090000_add_analytics_unknown_useragents.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-090000_add_analytics_unknown_useragents.php @@ -12,27 +12,29 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddAnalyticsUnknownUseragents extends Migration +class AddAnalyticsUnknownUseragents extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'id' => [ - 'type' => 'INT', - 'unsigned' => true, + 'type' => 'INT', + 'unsigned' => true, 'auto_increment' => true, ], 'useragent' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 255, - 'unique' => true, + 'unique' => true, ], 'hits' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'default' => 1, + 'default' => 1, ], ]); @@ -45,6 +47,7 @@ class AddAnalyticsUnknownUseragents extends Migration $this->forge->createTable('analytics_unknown_useragents'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_unknown_useragents'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-100000_add_analytics_podcasts_by_subscription.php b/modules/Analytics/Database/Migrations/2017-12-01-100000_add_analytics_podcasts_by_subscription.php index 24fd9783..44ea8c5e 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-100000_add_analytics_podcasts_by_subscription.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-100000_add_analytics_podcasts_by_subscription.php @@ -10,32 +10,34 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddAnalyticsPodcastsBySubscription extends Migration +class AddAnalyticsPodcastsBySubscription extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'podcast_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'episode_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'subscription_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'date' => [ 'type' => 'DATE', ], 'hits' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'default' => 1, + 'default' => 1, ], ]); @@ -48,6 +50,7 @@ class AddAnalyticsPodcastsBySubscription extends Migration $this->forge->createTable('analytics_podcasts_by_subscription'); } + #[Override] public function down(): void { $this->forge->dropTable('analytics_podcasts_by_subscription'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_podcasts_procedure.php b/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_podcasts_procedure.php index c87a0193..9472f0f2 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_podcasts_procedure.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_podcasts_procedure.php @@ -12,10 +12,12 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddAnalyticsPodcastsProcedure extends Migration +class AddAnalyticsPodcastsProcedure extends BaseMigration { + #[Override] public function up(): void { // Creates Procedure for data insertion @@ -85,6 +87,7 @@ class AddAnalyticsPodcastsProcedure extends Migration $this->db->query($createQuery); } + #[Override] public function down(): void { $prefix = $this->db->getPrefix(); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_unknown_useragents_procedure.php b/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_unknown_useragents_procedure.php index 13a3003d..af627bf2 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_unknown_useragents_procedure.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_unknown_useragents_procedure.php @@ -12,10 +12,12 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddAnalyticsUnknownUseragentsProcedure extends Migration +class AddAnalyticsUnknownUseragentsProcedure extends BaseMigration { + #[Override] public function up(): void { // Creates Procedure for data insertion @@ -33,6 +35,7 @@ class AddAnalyticsUnknownUseragentsProcedure extends Migration $this->db->query($createQuery); } + #[Override] public function down(): void { $procedureName = $this->db->prefixTable('analytics_unknown_useragents'); diff --git a/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_website_procedure.php b/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_website_procedure.php index c0668ad4..dc2b0bd2 100644 --- a/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_website_procedure.php +++ b/modules/Analytics/Database/Migrations/2017-12-01-210000_add_analytics_website_procedure.php @@ -12,10 +12,12 @@ declare(strict_types=1); namespace Modules\Analytics\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddAnalyticsWebsiteProcedure extends Migration +class AddAnalyticsWebsiteProcedure extends BaseMigration { + #[Override] public function up(): void { // Creates Procedure for data insertion @@ -51,6 +53,7 @@ class AddAnalyticsWebsiteProcedure extends Migration $this->db->query($createQuery); } + #[Override] public function down(): void { $procedureName = $this->db->prefixTable('analytics_website'); diff --git a/modules/Analytics/Entities/AnalyticsPodcasts.php b/modules/Analytics/Entities/AnalyticsPodcasts.php index 5b312210..45d4b624 100644 --- a/modules/Analytics/Entities/AnalyticsPodcasts.php +++ b/modules/Analytics/Entities/AnalyticsPodcasts.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace Modules\Analytics\Entities; use CodeIgniter\Entity\Entity; +use CodeIgniter\I18n\Time; /** * @property int $podcast_id @@ -27,7 +28,7 @@ use CodeIgniter\Entity\Entity; class AnalyticsPodcasts extends Entity { /** - * @var string[] + * @var list */ protected $dates = ['date', 'created_at', 'updated_at']; @@ -35,10 +36,10 @@ class AnalyticsPodcasts extends Entity * @var array */ protected $casts = [ - 'podcast_id' => 'integer', - 'duration' => 'double', - 'bandwidth' => 'integer', + 'podcast_id' => 'integer', + 'duration' => 'double', + 'bandwidth' => 'integer', 'unique_listeners' => 'integer', - 'hits' => 'integer', + 'hits' => 'integer', ]; } diff --git a/modules/Analytics/Entities/AnalyticsPodcastsByCountry.php b/modules/Analytics/Entities/AnalyticsPodcastsByCountry.php index 30b9dfb5..59ca02eb 100644 --- a/modules/Analytics/Entities/AnalyticsPodcastsByCountry.php +++ b/modules/Analytics/Entities/AnalyticsPodcastsByCountry.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace Modules\Analytics\Entities; use CodeIgniter\Entity\Entity; +use CodeIgniter\I18n\Time; /** * @property int $podcast_id @@ -26,7 +27,7 @@ use CodeIgniter\Entity\Entity; class AnalyticsPodcastsByCountry extends Entity { /** - * @var string[] + * @var list */ protected $dates = ['date', 'created_at', 'updated_at']; @@ -34,14 +35,11 @@ class AnalyticsPodcastsByCountry extends Entity * @var array */ protected $casts = [ - 'podcast_id' => 'integer', + 'podcast_id' => 'integer', 'country_code' => 'string', - 'hits' => 'integer', + 'hits' => 'integer', ]; - /** - * @noRector ReturnTypeDeclarationRector - */ public function getLabels(): string { return lang('Countries.' . $this->attributes['labels']); diff --git a/modules/Analytics/Entities/AnalyticsPodcastsByEpisode.php b/modules/Analytics/Entities/AnalyticsPodcastsByEpisode.php index cb461454..1aa5959a 100644 --- a/modules/Analytics/Entities/AnalyticsPodcastsByEpisode.php +++ b/modules/Analytics/Entities/AnalyticsPodcastsByEpisode.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace Modules\Analytics\Entities; use CodeIgniter\Entity\Entity; +use CodeIgniter\I18n\Time; /** * @property int $podcast_id @@ -25,7 +26,7 @@ use CodeIgniter\Entity\Entity; class AnalyticsPodcastsByEpisode extends Entity { /** - * @var string[] + * @var list */ protected $dates = ['date', 'created_at', 'updated_at']; @@ -35,6 +36,6 @@ class AnalyticsPodcastsByEpisode extends Entity protected $casts = [ 'podcast_id' => 'integer', 'episode_id' => 'integer', - 'hits' => 'integer', + 'hits' => 'integer', ]; } diff --git a/modules/Analytics/Entities/AnalyticsPodcastsByHour.php b/modules/Analytics/Entities/AnalyticsPodcastsByHour.php index 091fc720..bfc33b04 100644 --- a/modules/Analytics/Entities/AnalyticsPodcastsByHour.php +++ b/modules/Analytics/Entities/AnalyticsPodcastsByHour.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace Modules\Analytics\Entities; use CodeIgniter\Entity\Entity; +use CodeIgniter\I18n\Time; /** * @property int $podcast_id @@ -25,7 +26,7 @@ use CodeIgniter\Entity\Entity; class AnalyticsPodcastsByHour extends Entity { /** - * @var string[] + * @var list */ protected $dates = ['date', 'created_at', 'updated_at']; @@ -34,7 +35,7 @@ class AnalyticsPodcastsByHour extends Entity */ protected $casts = [ 'podcast_id' => 'integer', - 'hour' => 'integer', - 'hits' => 'integer', + 'hour' => 'integer', + 'hits' => 'integer', ]; } diff --git a/modules/Analytics/Entities/AnalyticsPodcastsByPlayer.php b/modules/Analytics/Entities/AnalyticsPodcastsByPlayer.php index 7c7faf15..8fb0acf7 100644 --- a/modules/Analytics/Entities/AnalyticsPodcastsByPlayer.php +++ b/modules/Analytics/Entities/AnalyticsPodcastsByPlayer.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace Modules\Analytics\Entities; use CodeIgniter\Entity\Entity; +use CodeIgniter\I18n\Time; /** * @property int $podcast_id @@ -28,7 +29,7 @@ use CodeIgniter\Entity\Entity; class AnalyticsPodcastsByPlayer extends Entity { /** - * @var string[] + * @var list */ protected $dates = ['date', 'created_at', 'updated_at']; @@ -37,10 +38,10 @@ class AnalyticsPodcastsByPlayer extends Entity */ protected $casts = [ 'podcast_id' => 'integer', - 'app' => '?string', - 'device' => '?string', - 'os' => '?string', - 'is_bot' => 'boolean', - 'hits' => 'integer', + 'app' => '?string', + 'device' => '?string', + 'os' => '?string', + 'is_bot' => 'boolean', + 'hits' => 'integer', ]; } diff --git a/modules/Analytics/Entities/AnalyticsPodcastsByRegion.php b/modules/Analytics/Entities/AnalyticsPodcastsByRegion.php index 5de71693..684667f3 100644 --- a/modules/Analytics/Entities/AnalyticsPodcastsByRegion.php +++ b/modules/Analytics/Entities/AnalyticsPodcastsByRegion.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace Modules\Analytics\Entities; use CodeIgniter\Entity\Entity; +use CodeIgniter\I18n\Time; /** * @property int $podcast_id @@ -28,7 +29,7 @@ use CodeIgniter\Entity\Entity; class AnalyticsPodcastsByRegion extends Entity { /** - * @var string[] + * @var list */ protected $dates = ['date', 'created_at', 'updated_at']; @@ -36,17 +37,14 @@ class AnalyticsPodcastsByRegion extends Entity * @var array */ protected $casts = [ - 'podcast_id' => 'integer', + 'podcast_id' => 'integer', 'country_code' => 'string', - 'region_code' => '?string', - 'latitude' => '?double', - 'longitude' => '?double', - 'hits' => 'integer', + 'region_code' => '?string', + 'latitude' => '?double', + 'longitude' => '?double', + 'hits' => 'integer', ]; - /** - * @noRector ReturnTypeDeclarationRector - */ public function getCountryCode(): string { return lang('Countries.' . $this->attributes['country_code']); diff --git a/modules/Analytics/Entities/AnalyticsPodcastsByService.php b/modules/Analytics/Entities/AnalyticsPodcastsByService.php index 51c19f8a..fb8a0366 100644 --- a/modules/Analytics/Entities/AnalyticsPodcastsByService.php +++ b/modules/Analytics/Entities/AnalyticsPodcastsByService.php @@ -13,7 +13,8 @@ declare(strict_types=1); namespace Modules\Analytics\Entities; use CodeIgniter\Entity\Entity; -use Opawg\UserAgentsPhp\UserAgentsRSS; +use CodeIgniter\I18n\Time; +use Opawg\UserAgentsV2Php\UserAgentsRSS; /** * @property int $podcast_id @@ -30,7 +31,7 @@ use Opawg\UserAgentsPhp\UserAgentsRSS; class AnalyticsPodcastsByService extends Entity { /** - * @var string[] + * @var list */ protected $dates = ['date', 'created_at', 'updated_at']; @@ -39,11 +40,11 @@ class AnalyticsPodcastsByService extends Entity */ protected $casts = [ 'podcast_id' => 'integer', - 'app' => '?string', - 'device' => '?string', - 'os' => '?string', - 'is_bot' => 'boolean', - 'hits' => 'integer', + 'app' => '?string', + 'device' => '?string', + 'os' => '?string', + 'is_bot' => 'boolean', + 'hits' => 'integer', ]; public function getLabels(): string diff --git a/modules/Analytics/Entities/AnalyticsPodcastsBySubscription.php b/modules/Analytics/Entities/AnalyticsPodcastsBySubscription.php index c48ba98b..09974b76 100644 --- a/modules/Analytics/Entities/AnalyticsPodcastsBySubscription.php +++ b/modules/Analytics/Entities/AnalyticsPodcastsBySubscription.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Modules\Analytics\Entities; use CodeIgniter\Entity\Entity; +use CodeIgniter\I18n\Time; /** * @property int $podcast_id @@ -24,7 +25,7 @@ use CodeIgniter\Entity\Entity; class AnalyticsPodcastsBySubscription extends Entity { /** - * @var string[] + * @var list */ protected $dates = ['date', 'created_at', 'updated_at']; @@ -32,9 +33,9 @@ class AnalyticsPodcastsBySubscription extends Entity * @var array */ protected $casts = [ - 'podcast_id' => 'integer', - 'episode_id' => 'integer', + 'podcast_id' => 'integer', + 'episode_id' => 'integer', 'subscription_id' => 'integer', - 'hits' => 'integer', + 'hits' => 'integer', ]; } diff --git a/modules/Analytics/Entities/AnalyticsUnknownUserAgent.php b/modules/Analytics/Entities/AnalyticsUnknownUserAgent.php index 34e85cae..016a3d0a 100644 --- a/modules/Analytics/Entities/AnalyticsUnknownUserAgent.php +++ b/modules/Analytics/Entities/AnalyticsUnknownUserAgent.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace Modules\Analytics\Entities; use CodeIgniter\Entity\Entity; +use CodeIgniter\I18n\Time; /** * @property int $id @@ -24,7 +25,7 @@ use CodeIgniter\Entity\Entity; class AnalyticsUnknownUserAgent extends Entity { /** - * @var string[] + * @var list */ protected $dates = ['created_at', 'updated_at']; @@ -32,8 +33,8 @@ class AnalyticsUnknownUserAgent extends Entity * @var array */ protected $casts = [ - 'id' => 'integer', + 'id' => 'integer', 'useragent' => 'integer', - 'hits' => 'integer', + 'hits' => 'integer', ]; } diff --git a/modules/Analytics/Entities/AnalyticsWebsiteByBrowser.php b/modules/Analytics/Entities/AnalyticsWebsiteByBrowser.php index 48ce16a1..9d764f7e 100644 --- a/modules/Analytics/Entities/AnalyticsWebsiteByBrowser.php +++ b/modules/Analytics/Entities/AnalyticsWebsiteByBrowser.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace Modules\Analytics\Entities; use CodeIgniter\Entity\Entity; +use CodeIgniter\I18n\Time; /** * @property int $podcast_id @@ -25,7 +26,7 @@ use CodeIgniter\Entity\Entity; class AnalyticsWebsiteByBrowser extends Entity { /** - * @var string[] + * @var list */ protected $dates = ['date', 'created_at', 'updated_at']; @@ -34,7 +35,7 @@ class AnalyticsWebsiteByBrowser extends Entity */ protected $casts = [ 'podcast_id' => 'integer', - 'browser' => 'string', - 'hits' => 'integer', + 'browser' => 'string', + 'hits' => 'integer', ]; } diff --git a/modules/Analytics/Entities/AnalyticsWebsiteByEntryPage.php b/modules/Analytics/Entities/AnalyticsWebsiteByEntryPage.php index f270c6c2..7e8294c2 100644 --- a/modules/Analytics/Entities/AnalyticsWebsiteByEntryPage.php +++ b/modules/Analytics/Entities/AnalyticsWebsiteByEntryPage.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace Modules\Analytics\Entities; use CodeIgniter\Entity\Entity; +use CodeIgniter\I18n\Time; /** * @property int $podcast_id @@ -25,7 +26,7 @@ use CodeIgniter\Entity\Entity; class AnalyticsWebsiteByEntryPage extends Entity { /** - * @var string[] + * @var list */ protected $dates = ['date', 'created_at', 'updated_at']; @@ -33,8 +34,8 @@ class AnalyticsWebsiteByEntryPage extends Entity * @var array */ protected $casts = [ - 'podcast_id' => 'integer', + 'podcast_id' => 'integer', 'entry_page_url' => 'string', - 'hits' => 'integer', + 'hits' => 'integer', ]; } diff --git a/modules/Analytics/Entities/AnalyticsWebsiteByReferer.php b/modules/Analytics/Entities/AnalyticsWebsiteByReferer.php index ef612549..c0edf9c8 100644 --- a/modules/Analytics/Entities/AnalyticsWebsiteByReferer.php +++ b/modules/Analytics/Entities/AnalyticsWebsiteByReferer.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace Modules\Analytics\Entities; use CodeIgniter\Entity\Entity; +use CodeIgniter\I18n\Time; /** * @property int $podcast_id @@ -25,7 +26,7 @@ use CodeIgniter\Entity\Entity; class AnalyticsWebsiteByReferer extends Entity { /** - * @var string[] + * @var list */ protected $dates = ['date', 'created_at', 'updated_at']; @@ -33,8 +34,8 @@ class AnalyticsWebsiteByReferer extends Entity * @var array */ protected $casts = [ - 'podcast_id' => 'integer', + 'podcast_id' => 'integer', 'referer_url' => 'string', - 'hits' => 'integer', + 'hits' => 'integer', ]; } diff --git a/modules/Analytics/Helpers/analytics_helper.php b/modules/Analytics/Helpers/analytics_helper.php index 351c8da7..adf97da4 100644 --- a/modules/Analytics/Helpers/analytics_helper.php +++ b/modules/Analytics/Helpers/analytics_helper.php @@ -9,9 +9,8 @@ declare(strict_types=1); */ use AdAures\Ipcat\IpDb; -use Config\Services; use GeoIp2\Database\Reader; -use Opawg\UserAgentsPhp\UserAgents; +use Opawg\UserAgentsV2Php\UserAgents; use WhichBrowser\Parser; if (! function_exists('base64_url_encode')) { @@ -34,42 +33,18 @@ if (! function_exists('base64_url_decode')) { } } -if (! function_exists('generate_episode_analytics_url')) { +if (! function_exists('client_ip')) { /** - * Builds the episode analytics url that redirects to the audio file url after analytics hit. + * Get the client IP, depending on available headers */ - function generate_episode_analytics_url( - int $podcastId, - int $episodeId, - string $podcastHandle, - string $episodeSlug, - string $audioExtension, - float $audioDuration, - int $audioFileSize, - int $audioFileHeaderSize, - \CodeIgniter\I18n\Time $publicationDate - ): string { - return url_to( - 'episode-analytics-hit', - base64_url_encode( - pack( - 'I*', - $podcastId, - $episodeId, - // bytes_threshold: number of bytes that must be downloaded for an episode to be counted in download analytics - // - if audio is less than or equal to 60s, then take the audio file_size - // - if audio is more than 60s, then take the audio file_header_size + 60s - $audioDuration <= 60 - ? $audioFileSize - : $audioFileHeaderSize + - floor((($audioFileSize - $audioFileHeaderSize) / $audioDuration) * 60), - $audioFileSize, - $audioDuration, - $publicationDate->getTimestamp(), - ), - ), - $podcastHandle . '/' . $episodeSlug . '.' . $audioExtension, - ); + function client_ip(): string + { + $superglobals = service('superglobals'); + if (! empty($superglobals->server('HTTP_X_FORWARDED_FOR'))) { + return $superglobals->server('HTTP_X_FORWARDED_FOR'); + } + + return $superglobals->server('REMOTE_ADDR'); } } @@ -79,11 +54,10 @@ if (! function_exists('set_user_session_deny_list_ip')) { */ function set_user_session_deny_list_ip(): void { - $session = Services::session(); - $session->start(); + $session = service('session'); if (! $session->has('denyListIp')) { - $session->set('denyListIp', IpDb::find($_SERVER['REMOTE_ADDR']) !== null); + $session->set('denyListIp', IpDb::find(client_ip()) !== null); } } } @@ -94,31 +68,26 @@ if (! function_exists('set_user_session_location')) { */ function set_user_session_location(): void { - $session = Services::session(); - $session->start(); + $session = service('session'); $location = [ 'countryCode' => 'N/A', - 'regionCode' => 'N/A', - 'latitude' => null, - 'longitude' => null, + 'regionCode' => 'N/A', + 'latitude' => null, + 'longitude' => null, ]; // Finds location: if (! $session->has('location')) { try { $cityReader = new Reader(WRITEPATH . 'uploads/GeoLite2-City/GeoLite2-City.mmdb'); - $city = $cityReader->city($_SERVER['REMOTE_ADDR']); + $city = $cityReader->city(client_ip()); $location = [ - 'countryCode' => $city->country->isoCode === null - ? 'N/A' - : $city->country->isoCode, - 'regionCode' => $city->subdivisions[0]->isoCode === null - ? 'N/A' - : $city->subdivisions[0]->isoCode, - 'latitude' => round($city->location->latitude, 3), - 'longitude' => round($city->location->longitude, 3), + 'countryCode' => $city->country->isoCode ?? 'N/A', + 'regionCode' => $city->subdivisions[0]->isoCode ?? 'N/A', + 'latitude' => round($city->location->latitude, 3), + 'longitude' => round($city->location->longitude, 3), ]; // If things go wrong the show must go on and the user must be able to download the file } catch (Exception) { @@ -135,12 +104,12 @@ if (! function_exists('set_user_session_player')) { */ function set_user_session_player(): void { - $session = Services::session(); - $session->start(); + $session = service('session'); if (! $session->has('player')) { $playerFound = null; - $userAgent = $_SERVER['HTTP_USER_AGENT']; + $userAgent = service('superglobals') + ->server('HTTP_USER_AGENT'); try { $playerFound = UserAgents::find($userAgent); @@ -152,10 +121,10 @@ if (! function_exists('set_user_session_player')) { $session->set('player', $playerFound); } else { $session->set('player', [ - 'app' => '- unknown -', + 'app' => '- unknown -', 'device' => '', - 'os' => '', - 'bot' => 0, + 'os' => '', + 'bot' => 0, ]); // Add to unknown list try { @@ -178,8 +147,7 @@ if (! function_exists('set_user_session_browser')) { */ function set_user_session_browser(): void { - $session = Services::session(); - $session->start(); + $session = service('session'); if (! $session->has('browser')) { $browserName = '- Other -'; @@ -205,14 +173,12 @@ if (! function_exists('set_user_session_referer')) { */ function set_user_session_referer(): void { - $session = Services::session(); - $session->start(); + $session = service('session'); - $newreferer = isset($_SERVER['HTTP_REFERER']) - ? $_SERVER['HTTP_REFERER'] - : '- Direct -'; + $newreferer = service('superglobals') + ->server('HTTP_REFERER') ?? '- Direct -'; $newreferer = - parse_url($newreferer, PHP_URL_HOST) === + parse_url((string) $newreferer, PHP_URL_HOST) === parse_url(current_url(false), PHP_URL_HOST) ? '- Direct -' : $newreferer; @@ -228,10 +194,10 @@ if (! function_exists('set_user_session_entry_page')) { */ function set_user_session_entry_page(): void { - $session = Services::session(); - $session->start(); + $session = service('session'); - $entryPage = $_SERVER['REQUEST_URI']; + $entryPage = service('superglobals') + ->server('REQUEST_URI'); if (! $session->has('entryPage')) { $session->set('entryPage', $entryPage); } @@ -243,12 +209,15 @@ if (! function_exists('podcast_hit')) { * Counting podcast episode downloads for analytic purposes ✅ No IP address is ever stored on the server. ✅ Only * aggregate data is stored in the database. We follow IAB Podcast Measurement Technical Guidelines Version 2.0: * https://iabtechlab.com/standards/podcast-measurement-guidelines/ - * https://iabtechlab.com/wp-content/uploads/2017/12/Podcast_Measurement_v2-Dec-20-2017.pdf ✅ 24-hour window ✅ - * Castopod does not do pre-load ✅ IP deny list https://github.com/client9/ipcat ✅ User-agent Filtering - * https://github.com/opawg/user-agents ✅ RSS User-agent https://github.com/opawg/podcast-rss-useragents ✅ - * Ignores 2 bytes range "Range: 0-1" (performed by official Apple iOS Podcast app) ✅ In case of partial content, - * adds up all requests to check >1mn was downloaded ✅ Identifying Uniques is done with a combination of IP - * Address and User Agent + * https://iabtechlab.com/wp-content/uploads/2017/12/Podcast_Measurement_v2-Dec-20-2017.pdf + * ✅ 24-hour window + * ✅ Castopod does not do pre-load + * ✅ IP deny list https://github.com/client9/ipcat + * ✅ User-agent Filtering https://github.com/opawg/user-agents-v2 + * ✅ RSS User-agent https://github.com/opawg/podcast-rss-useragents + * ✅ Ignores 2 bytes range "Range: 0-1" (performed by official Apple iOS Podcast app) + * ✅ In case of partial content, adds up all requests to check >1mn was downloaded + * ✅ Identifying Uniques is done with a combination of IP Address and User Agent * * @param integer $podcastId The podcast ID * @param integer $episodeId The Episode ID @@ -268,8 +237,9 @@ if (! function_exists('podcast_hit')) { string $serviceName, ?int $subscriptionId, ): void { - $session = Services::session(); - $session->start(); + $session = service('session'); + + $clientIp = client_ip(); // We try to count (but if things went wrong the show should go on and the user should be able to download the file): try { @@ -278,10 +248,9 @@ if (! function_exists('podcast_hit')) { $session->get('player')['bot'] = true; } + $superglobals = service('superglobals'); //We get the HTTP header field `Range`: - $httpRange = isset($_SERVER['HTTP_RANGE']) - ? $_SERVER['HTTP_RANGE'] - : null; + $httpRange = $superglobals->server('HTTP_RANGE') ?? null; $salt = config('Analytics') ->salt; @@ -290,12 +259,15 @@ if (! function_exists('podcast_hit')) { 'Analytics_Episode_' . sha1( $salt . '_' . date( - 'Y-m-d' - ) . '_' . $_SERVER['REMOTE_ADDR'] . '_' . $_SERVER['HTTP_USER_AGENT'] . '_' . $episodeId + 'Y-m-d', + ) . '_' . $clientIp . '_' . $superglobals->server('HTTP_USER_AGENT') . '_' . $episodeId, ); // The cache expires at midnight: $secondsToMidnight = strtotime('tomorrow') - time(); + + /** @var int|null $downloadedBytes */ $downloadedBytes = cache($episodeListenerHashId); + if ($downloadedBytes === null) { // If it was never downloaded that means that zero bytes were downloaded: $downloadedBytes = 0; @@ -311,13 +283,12 @@ if (! function_exists('podcast_hit')) { // [0-1] bytes range requests are used (by Apple) to check that file exists and that 206 partial content is working. // We don't count these requests. // We calculate how many bytes are being downloaded based on HTTP_RANGE values: - $ranges = explode(',', substr($httpRange, 6)); + $ranges = explode(',', substr((string) $httpRange, 6)); foreach ($ranges as $range) { $parts = explode('-', $range); $downloadedBytes += array_key_exists(1, $parts) ? $fileSize - : (int) $parts[1] - - (array_key_exists(0, $parts) ? 0 : (int) $parts[0]); + : (int) $parts[1] - (int) $parts[0]; } } @@ -337,14 +308,17 @@ if (! function_exists('podcast_hit')) { 'Analytics_Podcast_' . sha1( $salt . '_' . date( - 'Y-m-d' - ) . '_' . $_SERVER['REMOTE_ADDR'] . '_' . $_SERVER['HTTP_USER_AGENT'] . '_' . $podcastId + 'Y-m-d', + ) . '_' . $clientIp . '_' . $superglobals->server('HTTP_USER_AGENT') . '_' . $podcastId, ); $newListener = 1; + // Has this listener already downloaded an episode today: + /** @var int|null $downloadsByUser */ $downloadsByUser = cache($podcastListenerHashId); + // We add one download - if ($downloadsByUser) { + if ($downloadsByUser === null) { $newListener = 0; ++$downloadsByUser; } else { diff --git a/modules/Analytics/Models/AnalyticsPodcastByCountryModel.php b/modules/Analytics/Models/AnalyticsPodcastByCountryModel.php index cc96c415..b79d6de8 100644 --- a/modules/Analytics/Models/AnalyticsPodcastByCountryModel.php +++ b/modules/Analytics/Models/AnalyticsPodcastByCountryModel.php @@ -23,7 +23,7 @@ class AnalyticsPodcastByCountryModel extends Model protected $table = 'analytics_podcasts_by_country'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsPodcastsByCountry::class; @@ -52,7 +52,7 @@ class AnalyticsPodcastByCountryModel extends Model ->selectSum('hits', 'values') ->where([ 'podcast_id' => $podcastId, - 'date >' => $oneWeekAgo, + 'date >' => $oneWeekAgo, ]) ->groupBy('labels') ->orderBy('values', 'DESC') @@ -80,7 +80,7 @@ class AnalyticsPodcastByCountryModel extends Model ->selectSum('hits', 'values') ->where([ 'podcast_id' => $podcastId, - 'date >' => $oneYearAgo, + 'date >' => $oneYearAgo, ]) ->groupBy('labels') ->orderBy('values', 'DESC') diff --git a/modules/Analytics/Models/AnalyticsPodcastByEpisodeModel.php b/modules/Analytics/Models/AnalyticsPodcastByEpisodeModel.php index fa2e1fd7..cb4d47a9 100644 --- a/modules/Analytics/Models/AnalyticsPodcastByEpisodeModel.php +++ b/modules/Analytics/Models/AnalyticsPodcastByEpisodeModel.php @@ -23,7 +23,7 @@ class AnalyticsPodcastByEpisodeModel extends Model protected $table = 'analytics_podcasts_by_episode'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsPodcastsByEpisode::class; @@ -50,7 +50,7 @@ class AnalyticsPodcastByEpisodeModel extends Model ->where([ 'episode_id' => $episodeId, 'podcast_id' => $podcastId, - 'age <' => 60, + 'age <' => 60, ]) ->groupBy('labels') ->orderBy('labels', 'ASC') @@ -66,7 +66,7 @@ class AnalyticsPodcastByEpisodeModel extends Model /** * @return AnalyticsPodcastsByEpisode[] */ - public function getDataByMonth(int $podcastId, int $episodeId = null): array + public function getDataByMonth(int $podcastId, ?int $episodeId = null): array { if ( ! ($found = cache("{$podcastId}_{$episodeId}_analytics_podcast_by_episode_by_month")) diff --git a/modules/Analytics/Models/AnalyticsPodcastByHourModel.php b/modules/Analytics/Models/AnalyticsPodcastByHourModel.php index 5e627a75..071df0f9 100644 --- a/modules/Analytics/Models/AnalyticsPodcastByHourModel.php +++ b/modules/Analytics/Models/AnalyticsPodcastByHourModel.php @@ -23,7 +23,7 @@ class AnalyticsPodcastByHourModel extends Model protected $table = 'analytics_podcasts_by_hour'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsPodcastsByHour::class; @@ -49,7 +49,7 @@ class AnalyticsPodcastByHourModel extends Model ->selectSum('hits', 'values') ->where([ 'podcast_id' => $podcastId, - 'date >' => date('Y-m-d', strtotime('-60 days')), + 'date >' => date('Y-m-d', strtotime('-60 days')), ]) ->groupBy('labels') ->orderBy('labels', 'ASC') diff --git a/modules/Analytics/Models/AnalyticsPodcastByPlayerModel.php b/modules/Analytics/Models/AnalyticsPodcastByPlayerModel.php index d30e3488..1cc7d74d 100644 --- a/modules/Analytics/Models/AnalyticsPodcastByPlayerModel.php +++ b/modules/Analytics/Models/AnalyticsPodcastByPlayerModel.php @@ -23,7 +23,7 @@ class AnalyticsPodcastByPlayerModel extends Model protected $table = 'analytics_podcasts_by_player'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsPodcastsByPlayer::class; @@ -52,9 +52,9 @@ class AnalyticsPodcastByPlayerModel extends Model ->selectSum('hits', 'values') ->where([ 'podcast_id' => $podcastId, - 'app !=' => '', - 'is_bot' => 0, - 'date >' => $oneWeekAgo, + 'app !=' => '', + 'is_bot' => 0, + 'date >' => $oneWeekAgo, ]) ->groupBy('labels') ->orderBy('values', 'DESC') @@ -81,9 +81,9 @@ class AnalyticsPodcastByPlayerModel extends Model ->selectSum('hits', 'values') ->where([ 'podcast_id' => $podcastId, - 'app !=' => '', - 'is_bot' => 0, - 'date >' => $oneYearAgo, + 'app !=' => '', + 'is_bot' => 0, + 'date >' => $oneYearAgo, ]) ->groupBy('labels') ->orderBy('values', 'DESC') @@ -110,10 +110,10 @@ class AnalyticsPodcastByPlayerModel extends Model ->selectSum('hits', 'values') ->where([ 'podcast_id' => $podcastId, - 'app !=' => '', - 'os !=' => '', - 'is_bot' => 0, - 'date >' => $oneWeekAgo, + 'app !=' => '', + 'os !=' => '', + 'is_bot' => 0, + 'date >' => $oneWeekAgo, ]) ->groupBy('labels') ->orderBy('values', 'DESC') @@ -140,9 +140,9 @@ class AnalyticsPodcastByPlayerModel extends Model ->selectSum('hits', 'values') ->where([ 'podcast_id' => $podcastId, - 'device !=' => '', - 'is_bot' => 0, - 'date >' => $oneWeekAgo, + 'device !=' => '', + 'is_bot' => 0, + 'date >' => $oneWeekAgo, ]) ->groupBy('labels') ->orderBy('values', 'DESC') @@ -169,8 +169,8 @@ class AnalyticsPodcastByPlayerModel extends Model ->selectSum('hits', 'values') ->where([ 'podcast_id' => $podcastId, - 'is_bot' => 1, - 'date >' => $oneYearAgo, + 'is_bot' => 1, + 'date >' => $oneYearAgo, ]) ->groupBy('labels') ->orderBy('labels', 'ASC') diff --git a/modules/Analytics/Models/AnalyticsPodcastByRegionModel.php b/modules/Analytics/Models/AnalyticsPodcastByRegionModel.php index 7b04fd8a..0bba643b 100644 --- a/modules/Analytics/Models/AnalyticsPodcastByRegionModel.php +++ b/modules/Analytics/Models/AnalyticsPodcastByRegionModel.php @@ -23,7 +23,7 @@ class AnalyticsPodcastByRegionModel extends Model protected $table = 'analytics_podcasts_by_region'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsPodcastsByRegion::class; @@ -56,7 +56,7 @@ class AnalyticsPodcastByRegionModel extends Model ->groupBy('country_code, region_code') ->where([ 'podcast_id' => $podcastId, - 'date >' => date('Y-m-d', strtotime('-1 week')), + 'date >' => date('Y-m-d', strtotime('-1 week')), ]) ->orderBy('value', 'DESC') ->findAll(); diff --git a/modules/Analytics/Models/AnalyticsPodcastByServiceModel.php b/modules/Analytics/Models/AnalyticsPodcastByServiceModel.php index 2e1e9eda..e7bacc11 100644 --- a/modules/Analytics/Models/AnalyticsPodcastByServiceModel.php +++ b/modules/Analytics/Models/AnalyticsPodcastByServiceModel.php @@ -23,7 +23,7 @@ class AnalyticsPodcastByServiceModel extends Model protected $table = 'analytics_podcasts_by_player'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsPodcastsByService::class; @@ -53,8 +53,8 @@ class AnalyticsPodcastByServiceModel extends Model ->where([ 'podcast_id' => $podcastId, 'service !=' => '', - 'is_bot' => 0, - 'date >' => $oneWeekAgo, + 'is_bot' => 0, + 'date >' => $oneWeekAgo, ]) ->groupBy('labels') ->orderBy('values', 'DESC') diff --git a/modules/Analytics/Models/AnalyticsPodcastBySubscriptionModel.php b/modules/Analytics/Models/AnalyticsPodcastBySubscriptionModel.php index d845a08f..c629d057 100644 --- a/modules/Analytics/Models/AnalyticsPodcastBySubscriptionModel.php +++ b/modules/Analytics/Models/AnalyticsPodcastBySubscriptionModel.php @@ -21,7 +21,7 @@ class AnalyticsPodcastBySubscriptionModel extends Model protected $table = 'analytics_podcasts_by_subscription'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsPodcastsBySubscription::class; @@ -45,7 +45,7 @@ class AnalyticsPodcastBySubscriptionModel extends Model $found = (int) ($this->builder() ->selectSum('hits', 'total_hits') ->where([ - 'podcast_id' => $podcastId, + 'podcast_id' => $podcastId, 'subscription_id' => $subscriptionId, ]) ->where('`date` >= UTC_TIMESTAMP() - INTERVAL 3 month', null, false) diff --git a/modules/Analytics/Models/AnalyticsPodcastModel.php b/modules/Analytics/Models/AnalyticsPodcastModel.php index fee1ae44..751620e2 100644 --- a/modules/Analytics/Models/AnalyticsPodcastModel.php +++ b/modules/Analytics/Models/AnalyticsPodcastModel.php @@ -12,10 +12,9 @@ declare(strict_types=1); namespace Modules\Analytics\Models; -use App\Entities\Media\BaseMedia; -use App\Models\MediaModel; use CodeIgniter\Model; use Modules\Analytics\Entities\AnalyticsPodcasts; +use Modules\Media\Models\MediaModel; class AnalyticsPodcastModel extends Model { @@ -25,7 +24,7 @@ class AnalyticsPodcastModel extends Model protected $table = 'analytics_podcasts'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsPodcasts::class; @@ -50,7 +49,7 @@ class AnalyticsPodcastModel extends Model $found = $this->select('date as labels, hits as values') ->where([ 'podcast_id' => $podcastId, - 'date >' => date('Y-m-d', strtotime('-60 days')), + 'date >' => date('Y-m-d', strtotime('-60 days')), ]) ->orderBy('labels', 'ASC') ->findAll(); @@ -74,7 +73,7 @@ class AnalyticsPodcastModel extends Model ->selectSum('hits', 'values') ->where([ 'podcast_id' => $podcastId, - 'date >' => date('Y-m-d', strtotime('-60 days')), + 'date >' => date('Y-m-d', strtotime('-60 days')), ]) ->groupBy('labels, sort_labels') ->orderBy('sort_labels', 'ASC') @@ -98,7 +97,7 @@ class AnalyticsPodcastModel extends Model $found = $this->select('date as labels, ROUND(bandwidth / 1000000, 2) as `values`') ->where([ 'podcast_id' => $podcastId, - 'date >' => date('Y-m-d', strtotime('-60 days')), + 'date >' => date('Y-m-d', strtotime('-60 days')), ]) ->orderBy('labels', 'ASC') ->findAll(); @@ -147,7 +146,7 @@ class AnalyticsPodcastModel extends Model $found = $this->select('date as labels, unique_listeners as values') ->where([ 'podcast_id' => $podcastId, - 'date >' => date('Y-m-d', strtotime('-60 days')), + 'date >' => date('Y-m-d', strtotime('-60 days')), ]) ->orderBy('labels', 'ASC') ->findAll(); @@ -199,7 +198,7 @@ class AnalyticsPodcastModel extends Model ->selectSum('duration', 'values') ->where([ $this->table . '.podcast_id' => $podcastId, - 'date >' => date('Y-m-d', strtotime('-60 days')), + 'date >' => date('Y-m-d', strtotime('-60 days')), ]) ->groupBy('labels') ->orderBy('labels', 'ASC') @@ -247,7 +246,7 @@ class AnalyticsPodcastModel extends Model { if (! ($found = cache('analytics_total_bandwidth_by_month'))) { $found = $this->select( - 'DATE_FORMAT(updated_at,"%Y-%m") as labels, ROUND(sum(bandwidth) / 1000000, 2) as `values`' + 'DATE_FORMAT(updated_at,"%Y-%m") as labels, ROUND(sum(bandwidth) / 1000000, 2) as `values`', ) ->groupBy('labels') ->orderBy('labels', 'ASC') @@ -263,14 +262,13 @@ class AnalyticsPodcastModel extends Model /** * Get total storage * - * @return BaseMedia[] + * @return AnalyticsPodcasts[] */ public function getDataTotalStorageByMonth(): array { if (! ($found = cache('analytics_total_storage_by_month'))) { - $found = (new MediaModel())->select( - 'DATE_FORMAT(uploaded_at,"%Y-%m") as labels, ROUND(sum(file_size) / 1000000, 2) as `values`' - ) + $found = new MediaModel() + ->select('DATE_FORMAT(uploaded_at,"%Y-%m") as labels, ROUND(sum(file_size) / 1000000, 2) as `values`') ->groupBy('labels') ->orderBy('labels', 'ASC') ->findAll(); diff --git a/modules/Analytics/Models/AnalyticsUnknownUseragentsModel.php b/modules/Analytics/Models/AnalyticsUnknownUserAgentsModel.php similarity index 89% rename from modules/Analytics/Models/AnalyticsUnknownUseragentsModel.php rename to modules/Analytics/Models/AnalyticsUnknownUserAgentsModel.php index 21cf11d2..59053466 100644 --- a/modules/Analytics/Models/AnalyticsUnknownUseragentsModel.php +++ b/modules/Analytics/Models/AnalyticsUnknownUserAgentsModel.php @@ -15,7 +15,7 @@ namespace Modules\Analytics\Models; use CodeIgniter\Model; use Modules\Analytics\Entities\AnalyticsUnknownUserAgent; -class AnalyticsUnknownUserAgentModel extends Model +class AnalyticsUnknownUserAgentsModel extends Model { /** * @var string @@ -23,7 +23,7 @@ class AnalyticsUnknownUserAgentModel extends Model protected $table = 'analytics_unknown_useragents'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsUnknownUserAgent::class; diff --git a/modules/Analytics/Models/AnalyticsWebsiteByBrowserModel.php b/modules/Analytics/Models/AnalyticsWebsiteByBrowserModel.php index ebb446d6..e5398311 100644 --- a/modules/Analytics/Models/AnalyticsWebsiteByBrowserModel.php +++ b/modules/Analytics/Models/AnalyticsWebsiteByBrowserModel.php @@ -23,7 +23,7 @@ class AnalyticsWebsiteByBrowserModel extends Model protected $table = 'analytics_website_by_browser'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsWebsiteByBrowser::class; @@ -50,7 +50,7 @@ class AnalyticsWebsiteByBrowserModel extends Model ->selectSum('hits', 'values') ->where([ 'podcast_id' => $podcastId, - 'date >' => $oneWeekAgo, + 'date >' => $oneWeekAgo, ]) ->groupBy('labels') ->orderBy('values', 'DESC') diff --git a/modules/Analytics/Models/AnalyticsWebsiteByEntryPageModel.php b/modules/Analytics/Models/AnalyticsWebsiteByEntryPageModel.php index 0ef9d842..90bfcfde 100644 --- a/modules/Analytics/Models/AnalyticsWebsiteByEntryPageModel.php +++ b/modules/Analytics/Models/AnalyticsWebsiteByEntryPageModel.php @@ -23,7 +23,7 @@ class AnalyticsWebsiteByEntryPageModel extends Model protected $table = 'analytics_website_by_entry_page'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsWebsiteByEntryPage::class; @@ -50,7 +50,7 @@ class AnalyticsWebsiteByEntryPageModel extends Model ->selectSum('hits', 'values') ->where([ 'podcast_id' => $podcastId, - 'date >' => $oneWeekAgo, + 'date >' => $oneWeekAgo, ]) ->groupBy('labels') ->orderBy('values', 'DESC') diff --git a/modules/Analytics/Models/AnalyticsWebsiteByRefererModel.php b/modules/Analytics/Models/AnalyticsWebsiteByRefererModel.php index b5973d2c..cd8fb2a5 100644 --- a/modules/Analytics/Models/AnalyticsWebsiteByRefererModel.php +++ b/modules/Analytics/Models/AnalyticsWebsiteByRefererModel.php @@ -23,7 +23,7 @@ class AnalyticsWebsiteByRefererModel extends Model protected $table = 'analytics_website_by_referer'; /** - * @var string + * @var class-string */ protected $returnType = AnalyticsWebsiteByReferer::class; @@ -50,7 +50,7 @@ class AnalyticsWebsiteByRefererModel extends Model ->selectSum('hits', 'values') ->where([ 'podcast_id' => $podcastId, - 'date >' => $oneWeekAgo, + 'date >' => $oneWeekAgo, ]) ->groupBy('labels') ->orderBy('values', 'DESC') @@ -73,11 +73,11 @@ class AnalyticsWebsiteByRefererModel extends Model ! ($found = cache("{$podcastId}_analytics_website_by_domain_weekly")) ) { $oneWeekAgo = date('Y-m-d', strtotime('-1 week')); - $found = $this->select("SUBSTRING_INDEX(domain, '.', -2) as labels") + $found = $this->select('domain as labels') ->selectSum('hits', 'values') ->where([ 'podcast_id' => $podcastId, - 'date >' => $oneWeekAgo, + 'date >' => $oneWeekAgo, ]) ->groupBy('labels') ->orderBy('values', 'DESC') @@ -100,11 +100,11 @@ class AnalyticsWebsiteByRefererModel extends Model ! ($found = cache("{$podcastId}_analytics_website_by_domain_yearly")) ) { $oneYearAgo = date('Y-m-d', strtotime('-1 year')); - $found = $this->select("SUBSTRING_INDEX(domain, '.', -2) as labels") + $found = $this->select('domain as labels') ->selectSum('hits', 'values') ->where([ 'podcast_id' => $podcastId, - 'date >' => $oneYearAgo, + 'date >' => $oneYearAgo, ]) ->groupBy('labels') ->orderBy('values', 'DESC') diff --git a/modules/Api/Rest/V1/Config/Registrar.php b/modules/Api/Rest/V1/Config/Registrar.php new file mode 100644 index 00000000..43814934 --- /dev/null +++ b/modules/Api/Rest/V1/Config/Registrar.php @@ -0,0 +1,22 @@ + + */ + public static function Filters(): array + { + return [ + 'aliases' => [ + 'rest-api' => ApiFilter::class, + ], + ]; + } +} diff --git a/modules/Api/Rest/V1/Config/RestApi.php b/modules/Api/Rest/V1/Config/RestApi.php index d32a6cb9..836a8fb2 100644 --- a/modules/Api/Rest/V1/Config/RestApi.php +++ b/modules/Api/Rest/V1/Config/RestApi.php @@ -15,6 +15,17 @@ class RestApi extends BaseConfig */ public bool $enabled = false; + public bool $basicAuth = false; + + public ?string $basicAuthUsername = null; + + public ?string $basicAuthPassword = null; + + /** + * Default results limit. + */ + public int $limit = 10; + /** * -------------------------------------------------------------------------- * Rest API gateway diff --git a/modules/Api/Rest/V1/Config/Routes.php b/modules/Api/Rest/V1/Config/Routes.php index 0fe530ba..5f717129 100644 --- a/modules/Api/Rest/V1/Config/Routes.php +++ b/modules/Api/Rest/V1/Config/Routes.php @@ -4,18 +4,36 @@ declare(strict_types=1); namespace Modules\Api\Rest\V1\Config; -$routes = service('routes'); +use CodeIgniter\Router\RouteCollection; + +/** @var RouteCollection $routes */ $routes->group( config('RestApi') ->gateway . 'podcasts', [ 'namespace' => 'Modules\Api\Rest\V1\Controllers', - 'filter' => 'rest-api', + 'filter' => 'rest-api', ], static function ($routes): void { $routes->get('/', 'PodcastController::list'); $routes->get('(:num)', 'PodcastController::view/$1'); $routes->get('(:any)', 'ExceptionController::notFound'); - } + }, +); + +$routes->group( + config('RestApi') + ->gateway . 'episodes', + [ + 'namespace' => 'Modules\Api\Rest\V1\Controllers', + 'filter' => 'rest-api', + ], + static function ($routes): void { + $routes->get('/', 'EpisodeController::list'); + $routes->post('/', 'EpisodeController::attemptCreate'); + $routes->post('(:num)/publish', 'EpisodeController::attemptPublish/$1'); + $routes->get('(:num)', 'EpisodeController::view/$1'); + $routes->get('(:any)', 'ExceptionController::notFound'); + }, ); diff --git a/modules/Api/Rest/V1/Config/Services.php b/modules/Api/Rest/V1/Config/Services.php index c4a3d72c..40806e82 100644 --- a/modules/Api/Rest/V1/Config/Services.php +++ b/modules/Api/Rest/V1/Config/Services.php @@ -5,16 +5,16 @@ declare(strict_types=1); namespace Modules\Api\Rest\V1\Config; use CodeIgniter\Config\BaseService; -use Modules\Api\Rest\V1\Core\Exceptions; +use Modules\Api\Rest\V1\Core\RestApiExceptions; class Services extends BaseService { - public static function restApiExceptions(bool $getShared = true) + public static function restApiExceptions(bool $getShared = true): RestApiExceptions { if ($getShared) { return static::getSharedInstance('restApiExceptions'); } - return new Exceptions(config('Exceptions'), static::request(), static::response()); + return new RestApiExceptions(config('Exceptions')); } } diff --git a/modules/Api/Rest/V1/Controllers/BaseApiController.php b/modules/Api/Rest/V1/Controllers/BaseApiController.php new file mode 100644 index 00000000..ba114089 --- /dev/null +++ b/modules/Api/Rest/V1/Controllers/BaseApiController.php @@ -0,0 +1,22 @@ +initialize(); + } + + public function list(): ResponseInterface + { + $query = $this->request->getGet('query'); + $order = $this->request->getGet('order') ?? 'newest'; + $podcastIds = $this->request->getGet('podcastIds'); + + $builder = (new EpisodeModel()); + + if ($podcastIds !== null) { + $builder->whereIn('podcast_id', explode(',', (string) $podcastIds)); + } + + if ($query !== null) { + $builder->fullTextSearch($query); + + if ($order === 'search') { + $builder->orderBy('(episodes_score + podcasts_score)', 'desc'); + } + } + + if ($order === 'newest') { + $builder->orderBy('episodes.created_at', 'desc'); + } + + /** @var array $data */ + $data = $builder->findAll( + (int) ($this->request->getGet('limit') ?? config('RestApi')->limit), + (int) $this->request->getGet('offset'), + ); + + array_map(static function (Episode $episode): void { + self::mapEpisode($episode); + }, $data); + + return $this->respond($data); + } + + public function view(int $id): ResponseInterface + { + $episode = new EpisodeModel() + ->getEpisodeById($id); + + if (! $episode instanceof Episode) { + return $this->failNotFound('Episode not found'); + } + + // @phpstan-ignore-next-line + return $this->respond(static::mapEpisode($episode)); + } + + public function attemptCreate(): ResponseInterface + { + $rules = [ + 'created_by' => 'required|is_natural_no_zero', + 'updated_by' => 'required|is_natural_no_zero', + 'title' => 'required', + 'slug' => 'required|max_length[128]', + 'podcast_id' => 'required|is_natural_no_zero', + 'audio_file' => 'uploaded[audio_file]|ext_in[audio_file,mp3,m4a]', + 'cover' => 'permit_empty|is_image[cover]|ext_in[cover,jpg,jpeg,png]|min_dims[cover,1400,1400]|is_image_ratio[cover,1,1]', + 'transcript_file' => 'permit_empty|ext_in[transcript_file,srt,vtt]', + 'chapters_file' => 'permit_empty|ext_in[chapters_file,json]|is_json[chapters_file]', + 'transcript-choice' => 'permit_empty|in_list[upload-file,remote-url]', + 'chapters-choice' => 'permit_empty|in_list[upload-file,remote-url]', + ]; + + if (! $this->validate($rules)) { + return $this->failValidationErrors(array_values($this->validator->getErrors())); + } + + $podcastId = (int) $this->request->getPost('podcast_id'); + + $podcast = new PodcastModel() + ->getPodcastById($podcastId); + + if (! $podcast instanceof Podcast) { + return $this->failNotFound('Podcast not found'); + } + + $createdByUserId = (int) $this->request->getPost('created_by'); + + $userModel = new UserModel(); + $createdByUser = $userModel->find($createdByUserId); + + if (! $createdByUser) { + return $this->failNotFound('User not found'); + } + + $updatedByUserId = (int) $this->request->getPost('updated_by'); + + $updatedByUser = $userModel->find($updatedByUserId); + + if (! $updatedByUser) { + return $this->failNotFound('Updated by user not found'); + } + + if ($podcast->type === 'serial' && $this->request->getPost('type') === 'full') { + $rules['episode_number'] = 'required'; + } + + if (! $this->validate($rules)) { + return $this->failValidationErrors(array_values($this->validator->getErrors())); + } + + $validData = $this->validator->getValidated(); + + if (new EpisodeModel() + ->where([ + 'slug' => $validData['slug'], + 'podcast_id' => $podcast->id, + ]) + ->first() instanceof Episode) { + return $this->fail('An episode with the same slug already exists in this podcast.', 409); + } + + $newEpisode = new Episode([ + 'created_by' => $createdByUserId, + 'updated_by' => $updatedByUserId, + 'podcast_id' => $podcast->id, + 'title' => $validData['title'], + 'slug' => $validData['slug'], + 'guid' => null, + 'audio' => $this->request->getFile('audio_file'), + 'cover' => $this->request->getFile('cover'), + 'description_markdown' => $this->request->getPost('description'), + 'location' => in_array($this->request->getPost('location_name'), ['', null], true) + ? null + : new Location($this->request->getPost('location_name')), + 'parental_advisory' => $this->request->getPost('parental_advisory') !== 'undefined' + ? $this->request->getPost('parental_advisory') + : null, + 'number' => $this->request->getPost('episode_number') ? (int) $this->request->getPost( + 'episode_number', + ) : null, + 'season_number' => $this->request->getPost('season_number') ? (int) $this->request->getPost( + 'season_number', + ) : null, + 'type' => $this->request->getPost('type'), + 'is_blocked' => $this->request->getPost('block') === 'yes', + 'custom_rss_string' => $this->request->getPost('custom_rss'), + 'is_premium' => $this->request->getPost('premium') === 'yes', + 'published_at' => null, + ]); + + $transcriptChoice = $this->request->getPost('transcript-choice'); + if ($transcriptChoice === 'upload-file') { + $newEpisode->setTranscript($this->request->getFile('transcript_file')); + } elseif ($transcriptChoice === 'remote-url') { + $newEpisode->transcript_remote_url = $this->request->getPost( + 'transcript_remote_url', + ) === '' ? null : $this->request->getPost('transcript_remote_url'); + } + + $chaptersChoice = $this->request->getPost('chapters-choice'); + if ($chaptersChoice === 'upload-file') { + $newEpisode->setChapters($this->request->getFile('chapters_file')); + } elseif ($chaptersChoice === 'remote-url') { + $newEpisode->chapters_remote_url = $this->request->getPost( + 'chapters_remote_url', + ) === '' ? null : $this->request->getPost('chapters_remote_url'); + } + + $episodeModel = new EpisodeModel(); + if (($newEpisodeId = (int) $episodeModel->insert($newEpisode, true)) === 0) { + return $this->fail(array_values($episodeModel->errors()), 400); + } + + $episode = $episodeModel->find($newEpisodeId) + ->toRawArray(); + + return $this->respond($episode); + } + + public function attemptPublish(int $id): ResponseInterface + { + $episodeModel = new EpisodeModel(); + $episode = $episodeModel->getEpisodeById($id); + + if (! $episode instanceof Episode) { + return $this->failNotFound('Episode not found'); + } + + if ($episode->publication_status !== 'not_published') { + return $this->fail('Episode is already published or scheduled for publication', 409); + } + + $rules = [ + 'publication_method' => 'required', + 'created_by' => 'required|is_natural_no_zero', + ]; + + if (! $this->validate($rules)) { + return $this->failValidationErrors(array_values($this->validator->getErrors())); + } + + if ($this->request->getPost('publication_method') === 'schedule') { + $rules['scheduled_publication_date'] = 'required|valid_date[Y-m-d H:i]'; + } + + if (! $this->validate($rules)) { + return $this->failValidationErrors(array_values($this->validator->getErrors())); + } + + $createdByUserId = (int) $this->request->getPost('created_by'); + + $userModel = new UserModel(); + $createdByUser = $userModel->find($createdByUserId); + + if (! $createdByUser) { + return $this->failNotFound('User not found'); + } + + $validData = $this->validator->getValidated(); + + $db = db_connect(); + $db->transStart(); + + $newPost = new Post([ + 'actor_id' => $episode->podcast->actor_id, + 'episode_id' => $episode->id, + 'message' => $this->request->getPost('message') ?? '', + 'created_by' => $createdByUserId, + ]); + + $clientTimezone = $this->request->getPost('client_timezone') ?? app_timezone(); + + if ($episode->podcast->publication_status === 'published') { + $publishMethod = $validData['publication_method']; + if ($publishMethod === 'schedule') { + $scheduledPublicationDate = $validData['scheduled_publication_date'] ?? null; + if ($scheduledPublicationDate) { + $episode->published_at = Time::createFromFormat( + 'Y-m-d H:i', + $scheduledPublicationDate, + $clientTimezone, + )->setTimezone(app_timezone()); + } else { + $db->transRollback(); + return $this->fail('Scheduled publication date is required', 400); + } + } else { + $episode->published_at = Time::now(); + } + } elseif ($episode->podcast->publication_status === 'scheduled') { + // podcast publication date has already been set + $episode->published_at = $episode->podcast->published_at->addSeconds(1); + } else { + $episode->published_at = Time::now(); + } + + $newPost->published_at = $episode->published_at; + + $postModel = new PostModel(); + if (! $postModel->addPost($newPost)) { + $db->transRollback(); + return $this->fail(array_values($postModel->errors()), 400); + } + + if (! $episodeModel->update($episode->id, $episode)) { + $db->transRollback(); + return $this->fail(array_values($episodeModel->errors()), 400); + } + + $db->transComplete(); + + // @phpstan-ignore-next-line + return $this->respond(self::mapEpisode($episode)); + } + + protected static function mapEpisode(Episode $episode): Episode + { + $episode->cover_url = $episode->getCover() + ->file_url; + $episode->duration = round($episode->audio->duration); + + return $episode; + } +} diff --git a/modules/Api/Rest/V1/Controllers/ExceptionController.php b/modules/Api/Rest/V1/Controllers/ExceptionController.php index 50dd7863..3f1cd7f0 100644 --- a/modules/Api/Rest/V1/Controllers/ExceptionController.php +++ b/modules/Api/Rest/V1/Controllers/ExceptionController.php @@ -4,15 +4,11 @@ declare(strict_types=1); namespace Modules\Api\Rest\V1\Controllers; -use CodeIgniter\API\ResponseTrait; -use CodeIgniter\Controller; -use CodeIgniter\HTTP\Response; +use CodeIgniter\HTTP\ResponseInterface; -class ExceptionController extends Controller +class ExceptionController extends BaseApiController { - use ResponseTrait; - - public function notFound(): Response + public function notFound(): ResponseInterface { return $this->failNotFound('Podcast not found'); } diff --git a/modules/Api/Rest/V1/Controllers/PodcastController.php b/modules/Api/Rest/V1/Controllers/PodcastController.php index 3cbe3cd8..8daf7c33 100644 --- a/modules/Api/Rest/V1/Controllers/PodcastController.php +++ b/modules/Api/Rest/V1/Controllers/PodcastController.php @@ -6,37 +6,54 @@ namespace Modules\Api\Rest\V1\Controllers; use App\Entities\Podcast; use App\Models\PodcastModel; -use CodeIgniter\API\ResponseTrait; -use CodeIgniter\Controller; -use CodeIgniter\HTTP\Response; -use Modules\Api\Rest\V1\Config\Services; +use CodeIgniter\HTTP\ResponseInterface; -class PodcastController extends Controller +class PodcastController extends BaseApiController { - use ResponseTrait; - public function __construct() { - Services::restApiExceptions()->initialize(); + service('restApiExceptions')->initialize(); } - public function list(): Response + public function list(): ResponseInterface { - $data = (new PodcastModel())->findAll(); - array_map(static function ($podcast): void { - $podcast->feed_url = $podcast->getFeedUrl(); + /** @var array $data */ + $data = new PodcastModel() + ->findAll(); + array_map(static function (Podcast $podcast): void { + self::mapPodcast($podcast); }, $data); return $this->respond($data); } - public function view(int $id): Response + public function view(int $id): ResponseInterface { - $data = (new PodcastModel())->getPodcastById($id); - if (! $data instanceof Podcast) { + $podcast = new PodcastModel() + ->getPodcastById($id); + if (! $podcast instanceof Podcast) { return $this->failNotFound('Podcast not found'); } - $data->feed_url = $data->getFeedUrl(); - return $this->respond($data); + // @phpstan-ignore-next-line + return $this->respond(self::mapPodcast($podcast)); + } + + protected static function mapPodcast(Podcast $podcast): Podcast + { + $podcast->feed_url = $podcast->getFeedUrl(); + $podcast->actor_display_name = $podcast->getActor() + ->display_name; + $podcast->cover_url = $podcast->getCover() + ->file_url; + + $categories = [$podcast->getCategory(), ...$podcast->getOtherCategories()]; + + foreach ($categories as $category) { + $category->translated = lang('Podcast.category_options.' . $category->code); + } + + $podcast->categories = $categories; + + return $podcast; } } diff --git a/modules/Api/Rest/V1/Core/Exceptions.php b/modules/Api/Rest/V1/Core/RestApiExceptions.php similarity index 62% rename from modules/Api/Rest/V1/Core/Exceptions.php rename to modules/Api/Rest/V1/Core/RestApiExceptions.php index 4dae50e5..7d132aea 100644 --- a/modules/Api/Rest/V1/Core/Exceptions.php +++ b/modules/Api/Rest/V1/Core/RestApiExceptions.php @@ -4,16 +4,19 @@ declare(strict_types=1); namespace Modules\Api\Rest\V1\Core; +use CodeIgniter\Debug\Exceptions; +use Override; use Throwable; -class Exceptions extends \CodeIgniter\Debug\Exceptions +class RestApiExceptions extends Exceptions { + #[Override] protected function render(Throwable $exception, int $statusCode): void { header('Content-Type: application/json'); $data = [ - 'status' => $statusCode, - 'error' => $statusCode, + 'status' => $statusCode, + 'error' => $statusCode, 'messages' => [ 'error' => 'Unexpected error', ], @@ -21,9 +24,9 @@ class Exceptions extends \CodeIgniter\Debug\Exceptions if (ENVIRONMENT === 'development') { $data['messages'] = array_merge($data['messages'], [ 'message' => $exception->getMessage(), - 'file' => $exception->getFile(), - 'line' => $exception->getLine(), - 'trace' => $exception->getTrace(), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'trace' => $exception->getTrace(), ]); } diff --git a/modules/Api/Rest/V1/Filters/ApiFilter.php b/modules/Api/Rest/V1/Filters/ApiFilter.php index d6e6b32a..8bb25935 100644 --- a/modules/Api/Rest/V1/Filters/ApiFilter.php +++ b/modules/Api/Rest/V1/Filters/ApiFilter.php @@ -6,20 +6,75 @@ namespace Modules\Api\Rest\V1\Filters; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\Filters\FilterInterface; +use CodeIgniter\HTTP\Request; use CodeIgniter\HTTP\RequestInterface; +use CodeIgniter\HTTP\Response; use CodeIgniter\HTTP\ResponseInterface; +use Modules\Api\Rest\V1\Config\RestApi; +use Override; class ApiFilter implements FilterInterface { - public function before(RequestInterface $request, $arguments = null): void + /** + * @param Request $request + * + * @return RequestInterface|ResponseInterface|string|null + */ + #[Override] + public function before(RequestInterface $request, $arguments = null) { - if (! config('RestApi')->enabled) { + /** @var RestApi $restApiConfig */ + $restApiConfig = config('RestApi'); + + if (! $restApiConfig->enabled) { throw PageNotFoundException::forPageNotFound(); } + + if ($request->getMethod() === 'POST' && ! $restApiConfig->basicAuth) { + /** @var Response $response */ + $response = service('response'); + $response->setStatusCode(401); + return $response; + } + + if ($restApiConfig->basicAuth) { + /** @var Response $response */ + $response = service('response'); + if (! $request->hasHeader('Authorization')) { + $response->setStatusCode(401); + + return $response; + } + + $authHeader = $request->getHeaderLine('Authorization'); + if (! str_starts_with($authHeader, 'Basic ')) { + $response->setStatusCode(401); + + return $response; + } + + $auth_token = base64_decode(substr($authHeader, 6), true); + + [$username, $password] = explode(':', (string) $auth_token); + + if (! ($username === $restApiConfig->basicAuthUsername && $password === $restApiConfig->basicAuthPassword)) { + $response->setStatusCode(401); + + return $response; + } + } + + return null; } - public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void + /** + * @param string[]|null $arguments + * + * @return ResponseInterface|null + */ + #[Override] + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { - // Do something here + return null; } } diff --git a/modules/Api/Rest/V1/podcast.json b/modules/Api/Rest/V1/podcast.json deleted file mode 100644 index 4fe37fde..00000000 --- a/modules/Api/Rest/V1/podcast.json +++ /dev/null @@ -1,328 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "version": "1.0.0", - "title": "Castopod podcasts" - }, - "paths": { - "/api/rest/v1/podcasts": { - "get": { - "summary": "List all podcasts", - "responses": { - "200": { - "description": "Object of podcasts", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Podcasts" - } - } - } - }, - "default": { - "description": "unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/api/rest/v1/podcasts/{id}": { - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "The id of the podcast to retrieve", - "schema": { - "type": "integer", - "format": "int64", - "minimum": 1, - "maxLength": 10 - } - } - ], - "get": { - "summary": "Info for a specific podcast", - "responses": { - "200": { - "description": "Expected response to a valid request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Podcast" - } - } - } - }, - "default": { - "description": "unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "Podcast": { - "type": "object", - "required": [ - "id", - "guid", - "actor_id", - "handle", - "title", - "description_markdown", - "description_html", - "cover_id", - "language_code", - "category_id", - "owner_name", - "owner_email", - "type", - "is_blocked", - "is_completed", - "is_locked", - "is_published_on_hubs", - "created_by", - "updated_by", - "created_at", - "updated_at", - "feed_url" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64", - "minimum": 1, - "maxLength": 10 - }, - "guid": { - "type": "string", - "maxLength": 36 - }, - "actor_id": { - "type": "integer", - "format": "int64", - "minimum": 1, - "maxLength": 10 - }, - "handle": { - "type": "string", - "maxLength": 32 - }, - "title": { - "type": "string", - "maxLength": 128 - }, - "description_markdown": { - "type": "string" - }, - "description_html": { - "type": "string" - }, - "cover_id": { - "type": "integer", - "format": "int64", - "minimum": 1, - "maxLength": 10 - }, - "banner_id": { - "type": "integer", - "format": "int64", - "minimum": 1, - "maxLength": 10 - }, - "language_code": { - "type": "string", - "maxLength": 2 - }, - "category_id": { - "type": "integer", - "format": "int64", - "minimum": 1 - }, - "parental_advisory": { - "type": "string", - "enum": ["clean", "explicit"] - }, - "owner_name": { - "type": "string", - "maxLength": 128 - }, - "owner_email": { - "type": "string", - "maxLength": 255 - }, - "publisher": { - "type": "string", - "maxLength": 128 - }, - "type": { - "type": "string", - "enum": ["episodic", "serial"] - }, - "copyright": { - "type": "string", - "maxLength": 128 - }, - "episode_description_footer_markdown": { - "type": "string" - }, - "episode_description_footer_html": { - "type": "string" - }, - "is_blocked": { - "type": "integer", - "format": "int32", - "enum": [0, 1], - "minLength": 1 - }, - "is_completed": { - "type": "integer", - "format": "int32", - "enum": [0, 1], - "minLength": 1 - }, - "is_locked": { - "type": "integer", - "format": "int32", - "enum": [0, 1], - "minLength": 1 - }, - "imported_feed_url": { - "type": "string", - "maxLength": 512 - }, - "new_feed_url": { - "type": "string", - "maxLength": 512 - }, - "payment_pointer": { - "type": "string", - "maxLength": 128 - }, - "location_name": { - "type": "string", - "maxLength": 128 - }, - "location_geo": { - "type": "string", - "maxLength": 32 - }, - "location_osm": { - "type": "string", - "maxLength": 12 - }, - "custom_rss": { - "type": "string" - }, - "is_published_on_hubs": { - "type": "integer", - "format": "int32", - "enum": [0, 1], - "minLength": 1 - }, - "partner_id": { - "type": "string", - "maxLength": 32 - }, - "partner_link_url": { - "type": "string", - "maxLength": 512 - }, - "partner_image_url": { - "type": "string", - "maxLength": 512 - }, - "created_by": { - "type": "integer", - "format": "int64", - "minimum": 1, - "maxLength": 10 - }, - "updated_by": { - "type": "integer", - "format": "int64", - "minimum": 1, - "maxLength": 10 - }, - "created_at": { - "type": "object", - "properties": { - "date": { - "type": "string", - "format": "date-time" - }, - "timezone_type": { - "type": "integer", - "format": "int32" - }, - "timezone": { - "type": "string" - } - } - }, - "updated_at": { - "type": "object", - "properties": { - "date": { - "type": "string", - "format": "date-time" - }, - "timezone_type": { - "type": "integer", - "format": "int32" - }, - "timezone": { - "type": "string" - } - } - }, - "feed_url": { - "type": "string" - } - } - }, - "Podcasts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Podcast" - } - }, - "Error": { - "type": "object", - "properties": { - "status": { - "type": "integer", - "format": "int32" - }, - "error": { - "type": "integer", - "format": "int32" - }, - "messages": { - "type": "object", - "properties": { - "error": { - "type": "string" - } - } - } - } - } - } - } -} diff --git a/modules/Api/Rest/V1/schema.yaml b/modules/Api/Rest/V1/schema.yaml new file mode 100644 index 00000000..9c7a0220 --- /dev/null +++ b/modules/Api/Rest/V1/schema.yaml @@ -0,0 +1,530 @@ +openapi: 3.1.0 +info: + version: 1.0.0 + title: Castopod API + description: |- + The Castopod API offers you a programmatic way to integrate your Podcasts and + Episodes in your apps and help you automate creation and publishing. + + ⚠️ **The API is disabled by default.** + + You may add the following feature flag in your `.env` to activate it: + + ```ini + restapi.enabled=true + ``` + + Operations to add or publish episodes require you to setup basic authentication + in your `.env`: + + ```ini + restapi.basicAuth=true + restapi.basicAuthUsername="YOUR_BASIC_AUTH_USERNAME" + restapi.basicAuthPassword="YOUR_BASIC_AUTH_PASSWORD" + ``` + + With BasicAuth enabled, your requests must include the `Authorization` header + with the username and password you have set previously: + + ``` + "Authorization": "Basic username:password" + ``` + + license: + name: AGPL v3 + url: https://code.castopod.org/adaures/castopod/-/blob/develop/LICENSE.md +paths: + /podcasts: + get: + summary: Get all podcasts + operationId: get-all-podcasts + responses: + "200": + description: Successful operation + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Podcast" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /podcasts/{podcastId}: + parameters: + - name: podcastId + in: path + description: The id of the podcast to retrieve + required: true + schema: + type: integer + format: int64 + get: + summary: Get podcast by ID + description: Returns a single podcast + operationId: get-podcast-by-id + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Podcast" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /episodes: + get: + summary: Get all episodes + operationId: get-all-episodes + responses: + "200": + description: Object of episodes + content: + application/json: + schema: + type: "array" + items: + $ref: "#/components/schemas/Episode" + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Add a new episode + operationId: add-episode + requestBody: + description: Episode object that needs to be added + required: true + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/EpisodeCreateRequest" + responses: + "201": + description: Episode created successfully + content: + application/json: + schema: + $ref: "#/components/schemas/Episode" + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /episodes/{episodeId}: + parameters: + - name: episodeId + in: path + description: The id of the episode to retrieve + required: true + schema: + type: integer + format: int64 + get: + summary: Get episode by ID + description: Returns a single episode + operationId: get-episode-by-id + responses: + "200": + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Episode" + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /episodes/{episodeId}/publish: + post: + summary: Publish an episode + operationId: publish-episode + parameters: + - name: episodeId + in: path + description: The id of the episode to publish + required: true + schema: + type: integer + format: int64 + requestBody: + description: Publish parameters + required: true + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/EpisodePublishRequest" + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Episode" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Podcast: + type: object + required: + - id + - guid + - actor_id + - handle + - title + - description_markdown + - description_html + - cover_id + - language_code + - category_id + - owner_name + - owner_email + - type + - is_blocked + - is_completed + - is_locked + - is_published_on_hubs + - created_by + - updated_by + - created_at + - updated_at + - feed_url + properties: + id: + type: integer + format: int64 + guid: + type: string + maxLength: 36 + actor_id: + type: integer + format: int64 + handle: + type: string + maxLength: 32 + title: + type: string + maxLength: 128 + description_markdown: + type: string + description_html: + type: string + cover_id: + type: integer + format: int64 + banner_id: + type: integer + format: int64 + language_code: + type: string + maxLength: 2 + category_id: + type: integer + format: int64 + minimum: 1 + parental_advisory: + type: string + enum: + - clean + - explicit + owner_name: + type: string + maxLength: 128 + owner_email: + type: string + maxLength: 255 + publisher: + type: string + maxLength: 128 + type: + type: string + enum: + - episodic + - serial + copyright: + type: string + maxLength: 128 + episode_description_footer_markdown: + type: string + episode_description_footer_html: + type: string + is_blocked: + type: integer + format: int32 + enum: + - 0 + - 1 + is_completed: + type: integer + format: int32 + enum: + - 0 + - 1 + is_locked: + type: integer + format: int32 + enum: + - 0 + - 1 + imported_feed_url: + type: string + maxLength: 512 + new_feed_url: + type: string + maxLength: 512 + location_name: + type: string + maxLength: 128 + location_geo: + type: string + maxLength: 32 + location_osm: + type: string + maxLength: 12 + custom_rss: + type: string + is_published_on_hubs: + type: integer + format: int32 + enum: + - 0 + - 1 + partner_id: + type: string + maxLength: 32 + partner_link_url: + type: string + maxLength: 512 + partner_image_url: + type: string + maxLength: 512 + created_by: + type: integer + format: int64 + updated_by: + type: integer + format: int64 + created_at: + type: object + properties: + date: + type: string + format: date-time + timezone_type: + type: integer + format: int32 + timezone: + type: string + updated_at: + type: object + properties: + date: + type: string + format: date-time + timezone_type: + type: integer + format: int32 + timezone: + type: string + feed_url: + type: string + Episode: + type: object + required: + - id + - title + - slug + - podcast_id + - created_by + - updated_by + - created_at + - updated_at + properties: + id: + type: integer + format: int64 + title: + type: string + slug: + type: string + podcast_id: + type: integer + format: int64 + description_markdown: + type: string + description_html: + type: string + audio_url: + type: string + format: uri + cover_url: + type: string + format: uri + duration: + type: integer + format: int32 + published_at: + type: string + format: date-time + created_by: + type: integer + format: int64 + updated_by: + type: integer + format: int64 + EpisodeCreateRequest: + type: object + required: + - user_id + - updated_by + - title + - slug + - podcast_id + - audio_file + properties: + user_id: + type: integer + format: int64 + description: ID of the user creating the episode + updated_by: + type: integer + format: int64 + description: ID of the user updating the episode + title: + type: string + description: Title of the episode + slug: + type: string + description: URL-friendly slug of the episode + podcast_id: + type: integer + format: int64 + description: ID of the podcast the episode belongs to + audio_file: + type: string + format: binary + description: Audio file for the episode + cover: + type: string + format: binary + description: Cover image for the episode + description: + type: string + description: Description of the episode + location_name: + type: string + description: Location associated with the episode + parental_advisory: + type: string + enum: + - clean + - explicit + description: Parental advisory rating + episode_number: + type: integer + format: int32 + description: Episode number (for serial podcasts) + season_number: + type: integer + format: int32 + description: Season number (for serial podcasts) + type: + type: string + enum: + - full + - trailer + - bonus + description: Type of episode + block: + type: string + enum: + - "yes" + - "no" + description: Block episode from being published + custom_rss: + type: string + description: Custom RSS content + premium: + type: string + enum: + - "yes" + - "no" + description: Mark episode as premium content + transcript-choice: + type: string + enum: + - upload-file + - remote-url + description: Transcript source choice + transcript_file: + type: string + format: binary + description: Transcript file + transcript_remote_url: + type: string + format: uri + description: Remote URL for transcript + chapters-choice: + type: string + enum: + - upload-file + - remote-url + description: Chapters source choice + chapters_file: + type: string + format: binary + description: Chapters file + chapters_remote_url: + type: string + format: uri + description: Remote URL for chapters + EpisodePublishRequest: + type: object + required: + - publication_method + properties: + publication_method: + type: string + enum: + - now + - schedule + - with_podcast + description: Method of publication + scheduled_publication_date: + type: string + format: date-time + description: Scheduled date and time for publication + client_timezone: + type: string + description: Timezone of the client + Error: + type: object + properties: + status: + type: integer + format: int32 + error: + type: integer + format: int32 + messages: + type: object + properties: + error: + type: string diff --git a/modules/Auth/Auth.php b/modules/Auth/Auth.php new file mode 100644 index 00000000..83403106 --- /dev/null +++ b/modules/Auth/Auth.php @@ -0,0 +1,46 @@ +routes($routes); + * - auth()->routes($routes, ['except' => ['login', 'register']]) + * + * @param array{except?:list} $config + */ + #[Override] + public function routes(RouteCollection &$routes, array $config = []): void + { + $authRoutes = config('AuthRoutes') + ->routes; + + $routes->group(config('Auth')->gateway, [ + 'namespace' => 'Modules\Auth\Controllers', + ], static function (RouteCollection $routes) use ($authRoutes, $config): void { + foreach ($authRoutes as $name => $row) { + if (! isset($config['except']) || ! in_array($name, $config['except'], true)) { + foreach ($row as $params) { + $options = isset($params[3]) + ? [ + 'as' => $params[3], + ] + : null; + $routes->{$params[0]}($params[1], $params[2], $options); + } + } + } + }); + } +} diff --git a/modules/Auth/Authorization/FlatAuthorization.php b/modules/Auth/Authorization/FlatAuthorization.php deleted file mode 100644 index 933a2728..00000000 --- a/modules/Auth/Authorization/FlatAuthorization.php +++ /dev/null @@ -1,54 +0,0 @@ -getPermissionID($permission); - - if (! is_numeric($permissionId)) { - return false; - } - - return $this->permissionModel->doesGroupHavePermission($groupId, $permissionId); - } - - /** - * Makes user part of given groups. - * - * @param array $groups Either collection of ID or names - */ - public function setUserGroups(int $userId, array $groups = []): bool - { - // remove user from all groups before resetting it in new groups - $this->groupModel->removeUserFromAllGroups($userId); - - if ($groups === []) { - return true; - } - - foreach ($groups as $group) { - $this->addUserToGroup($userId, $group); - } - - return true; - } -} diff --git a/modules/Auth/Authorization/GroupModel.php b/modules/Auth/Authorization/GroupModel.php deleted file mode 100644 index 74618542..00000000 --- a/modules/Auth/Authorization/GroupModel.php +++ /dev/null @@ -1,30 +0,0 @@ -select('auth_groups.*') - ->like('name', 'podcast_', 'after') - ->findAll(); - } - - /** - * @return mixed[] - */ - public function getUserRoles(): array - { - return $this->select('auth_groups.*') - ->notLike('name', 'podcast_', 'after') - ->findAll(); - } -} diff --git a/modules/Auth/Authorization/PermissionModel.php b/modules/Auth/Authorization/PermissionModel.php deleted file mode 100644 index 01106c10..00000000 --- a/modules/Auth/Authorization/PermissionModel.php +++ /dev/null @@ -1,53 +0,0 @@ -getPermissionsForGroup($groupId); - - return count($groupPerms) && - array_key_exists($permissionId, $groupPerms); - } - - /** - * Gets all permissions for a group in a way that can be easily used to check against: - * - * [ id => name, id => name ] - * - * @return array - */ - public function getPermissionsForGroup(int $groupId): array - { - $cacheName = "group{$groupId}_permissions"; - if (! ($found = cache($cacheName))) { - $groupPermissions = $this->db - ->table('auth_groups_permissions') - ->select('id, auth_permissions.name') - ->join('auth_permissions', 'auth_permissions.id = permission_id', 'inner') - ->where('group_id', $groupId) - ->get() - ->getResultObject(); - - $found = []; - foreach ($groupPermissions as $row) { - $found[$row->id] = strtolower($row->name); - } - - cache() - ->save($cacheName, $found, 300); - } - - return $found; - } -} diff --git a/modules/Auth/Commands/RolesDoc.php b/modules/Auth/Commands/RolesDoc.php new file mode 100644 index 00000000..84d7b941 --- /dev/null +++ b/modules/Auth/Commands/RolesDoc.php @@ -0,0 +1,196 @@ + + */ + private const array COMMENT_BLOCK_IDS = [ + 'instance_roles' => 'AUTH-INSTANCE-ROLES-LIST', + 'instance_permissions' => 'AUTH-INSTANCE-PERMISSIONS-LIST', + 'podcast_roles' => 'AUTH-PODCAST-ROLES-LIST', + 'podcast_permissions' => 'AUTH-PODCAST-PERMISSIONS-LIST', + ]; + + /** + * @var string + */ + protected $group = 'auth'; + + /** + * @var string + */ + protected $name = 'auth:generate-doc'; + + /** + * @var string + */ + protected $description = 'Generates the html table references for roles and permissions in the docs.'; + + #[Override] + public function run(array $params): void + { + // loop over all files in path + $files = glob(ROOTPATH . 'docs/src/content/docs/**/getting-started/auth.mdx'); + + if (! $files) { + $files = []; + } + + CLI::write(implode(PHP_EOL, $files)); + + if ($files === []) { + return; + } + + foreach ($files as $file) { + $locale = $this->detectLocaleFromPath($file); + service('language') + ->setLocale($locale); + + $authGroups = new AuthGroups(); + + $fileContents = file_get_contents($file); + + foreach (self::COMMENT_BLOCK_IDS as $key => $block_id) { + $pattern = '/(\{\/\*\s' . $block_id . ':START.*\*\/\})[\S\s]*(\{\/\*\s' . $block_id . ':END.*\*\/\})/'; + + $handleInjectMethod = 'handle' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $key))); + + $fileContents = $this->{$handleInjectMethod}($authGroups, $fileContents, $pattern); + } + + // Write the contents back to the file + file_put_contents($file, $fileContents); + } + } + + protected function handleInstanceRoles(AuthGroups $authGroups, string $fileContents, string $pattern): string + { + $instanceMatrix = $authGroups->matrix; + return $this->renderCommentBlock( + $fileContents, + $pattern, + ['role', 'description', 'permissions'], + $authGroups->instanceGroups, + static function ($table, $key, array $value) use ($instanceMatrix): void { + $table->addRow($value['title'], $value['description'], implode(', ', $instanceMatrix[$key])); + }, + ); + } + + protected function handleInstancePermissions(AuthGroups $authGroups, string $fileContents, string $pattern): string + { + return $this->renderCommentBlock( + $fileContents, + $pattern, + ['permission', 'description'], + $authGroups->instancePermissions, + static function ($table, $key, $value): void { + $table->addRow($key, $value); + }, + ); + } + + protected function handlePodcastRoles(AuthGroups $authGroups, string $fileContents, string $pattern): string + { + $podcastMatrix = $authGroups->podcastMatrix; + return $this->renderCommentBlock( + $fileContents, + $pattern, + ['role', 'description', 'permissions'], + $authGroups->podcastGroups, + static function ($table, $key, array $value) use ($podcastMatrix): void { + $table->addRow($value['title'], $value['description'], implode(', ', $podcastMatrix[$key])); + }, + ); + } + + protected function handlePodcastPermissions(AuthGroups $authGroups, string $fileContents, string $pattern): string + { + return $this->renderCommentBlock( + $fileContents, + $pattern, + ['permission', 'description'], + $authGroups->podcastPermissions, + static function ($table, $key, $value): void { + $table->addRow($key, $value); + }, + ); + } + + /** + * @param array $tableHeading + * @param array|array> $data + */ + private function renderCommentBlock( + string $fileContents, + string $pattern, + array $tableHeading, + array $data, + Closure $callback, + ): string { + // check if it has the start and end comments to insert roles table + // looking for and + + $hasInstanceInsertComments = preg_match($pattern, $fileContents); + + if (! $hasInstanceInsertComments) { + return $fileContents; + } + + // prepare role table + $table = new Table(); + $table->setHeading($tableHeading); + + foreach ($data as $key => $value) { + $callback($table, $key, $value); + } + + $converter = new HtmlConverter(); + $converter->getEnvironment() + ->addConverter(new TableConverter()); + $markdownTable = str_replace(['{', '}'], ['\{', '\}'], $converter->convert($table->generate())); + + // insert table between block comments + $newFileContents = preg_replace( + $pattern, + '${1}' . PHP_EOL . PHP_EOL . $markdownTable . PHP_EOL . PHP_EOL . '${2}', + $fileContents, + ); + + if ($newFileContents === null) { + return $fileContents; + } + + return $newFileContents; + } + + private function detectLocaleFromPath(string $fileKey): string + { + preg_match( + '~docs\/src\/content\/docs\/(?:([a-z]{2}(?:-[A-Za-z]{2,})?)\/)getting-started\/auth\.mdx~', + $fileKey, + $match, + ); + + if ($match === []) { + return 'en'; + } + + return $match[1]; + } +} diff --git a/modules/Auth/Config/Auth.php b/modules/Auth/Config/Auth.php index 11ffea8a..f5fc2c68 100644 --- a/modules/Auth/Config/Auth.php +++ b/modules/Auth/Config/Auth.php @@ -4,46 +4,114 @@ declare(strict_types=1); namespace Modules\Auth\Config; -use Myth\Auth\Config\Auth as MythAuthConfig; +use CodeIgniter\Shield\Authentication\Actions\ActionInterface; +use CodeIgniter\Shield\Authentication\Actions\Email2FA; +use CodeIgniter\Shield\Authentication\AuthenticatorInterface; +use CodeIgniter\Shield\Config\Auth as ShieldAuth; +use CodeIgniter\Shield\Entities\User; +use Modules\Auth\Models\UserModel; +use Override; -class Auth extends MythAuthConfig +class Auth extends ShieldAuth { /** - * -------------------------------------------------------------------------- - * Views used by Auth Controllers - * -------------------------------------------------------------------------- + * ////////////////// AUTHENTICATION ////////////////// * * @var array */ - public $views = [ - 'login' => 'login', - 'register' => 'register', - 'forgot' => 'forgot', - 'reset' => 'reset', - 'emailForgot' => 'emails/forgot', - 'emailActivation' => 'emails/activation', + public array $views = [ + 'login' => 'login', + 'register' => 'register', + 'layout' => '_layout', + 'action_email_2fa' => 'email_2fa_show', + 'action_email_2fa_verify' => 'email_2fa_verify', + 'action_email_2fa_email' => 'emails/email_2fa_email', + 'action_email_activate_show' => 'email_activate_show', + 'action_email_activate_email' => 'emails/email_activate_email', + 'magic-link-login' => 'magic_link_form', + 'magic-link-message' => 'magic_link_message', + 'magic-link-email' => 'emails/magic_link_email', + 'magic-link-set-password' => 'magic_link_set_password', + 'welcome-email' => 'emails/welcome_email', ]; /** - * -------------------------------------------------------------------------- - * Layout for the views to extend - * -------------------------------------------------------------------------- + * -------------------------------------------------------------------- + * Redirect urLs + * -------------------------------------------------------------------- + * The default URL that a user will be redirected to after + * various auth actions. If you need more flexibility you can + * override the `getUrl()` method to apply any logic you may need. * - * @var string + * @var array */ - public $viewLayout = '_layout'; + public array $redirects = [ + 'register' => '/', + 'login' => '/', + 'logout' => 'login', + 'force_reset' => '/', + 'permission_denied' => '/', + 'group_denied' => '/', + ]; /** - * -------------------------------------------------------------------------- - * Allow User Registration - * -------------------------------------------------------------------------- - * When enabled (default) any unregistered user may apply for a new - * account. If you disable registration you may need to ensure your - * controllers and views know not to offer registration. + * -------------------------------------------------------------------- + * Authentication Actions + * -------------------------------------------------------------------- + * Specifies the class that represents an action to take after + * the user logs in or registers a new account at the site. * - * @var bool + * You must register actions in the order of the actions to be performed. + * + * Available actions with Shield: + * - register: 'CodeIgniter\Shield\Authentication\Actions\EmailActivator' + * - login: 'CodeIgniter\Shield\Authentication\Actions\Email2FA' + * + * @var array|null> */ - public $allowRegistration = false; + public array $actions = [ + 'register' => null, + 'login' => null, + ]; + + /** + * -------------------------------------------------------------------- + * Allow Registration + * -------------------------------------------------------------------- + * Determines whether users can register for the site. + */ + public bool $allowRegistration = false; + + /** + * -------------------------------------------------------------------- + * Allow Two-Factor Authentication + * -------------------------------------------------------------------- + * Determines whether email 2FA is enabled. + */ + public bool $enable2FA = false; + + /** + * -------------------------------------------------------------------- + * Welcome Link Lifetime + * -------------------------------------------------------------------- + * Specifies the amount of time, in seconds, that a welcome link is valid. + * You can use Time Constants or any desired number. + */ + public int $welcomeLinkLifetime = 48 * HOUR; + + /** + * -------------------------------------------------------------------- + * User Provider + * -------------------------------------------------------------------- + * The name of the class that handles user persistence. + * By default, this is the included UserModel, which + * works with any of the database engines supported by CodeIgniter. + * You can change it as long as they adhere to the + * CodeIgniter\Shield\Models\UserModel. + * + * @var class-string + */ + public string $userProvider = UserModel::class; /** * -------------------------------------------------------------------------- @@ -52,4 +120,57 @@ class Auth extends MythAuthConfig * Defines a base route for all authentication related pages */ public string $gateway = 'cp-auth'; + + public function __construct() + { + parent::__construct(); + + $adminGateway = config('Admin') + ->gateway; + + $this->redirects = [ + 'register' => $adminGateway, + 'login' => $adminGateway, + 'logout' => $adminGateway, + 'force_reset' => $adminGateway, + 'permission_denied' => $adminGateway, + 'group_denied' => $adminGateway, + ]; + + // FIXME: enable2FA config can only be updated in the .env + // Using the settings service to have it set in the db causes infinite loop. + if ($this->enable2FA) { + $this->actions['login'] = Email2FA::class; + } + } + + /** + * Returns the URL that a user should be redirected to after a successful login. + * + * Redirects to the set-password form if magicLogin + */ + #[Override] + public function loginRedirect(): string + { + if (! session('magicLogin')) { + return $this->getUrl(setting('Auth.redirects')['login']); + } + + // activate user upon magic-link login as it is done via email + if (! auth()->user()->active) { + /** @var AuthenticatorInterface $authenticator */ + $authenticator = auth('session') + ->getAuthenticator(); + + $user = $authenticator->getUser(); + + if ($user instanceof User) { + // Set the user active now + $user->activate(); + } + } + + // prompt user to change their password + return $this->getUrl(route_to('magic-link-set-password')); + } } diff --git a/modules/Auth/Config/AuthGroups.php b/modules/Auth/Config/AuthGroups.php new file mode 100644 index 00000000..5bb64b88 --- /dev/null +++ b/modules/Auth/Config/AuthGroups.php @@ -0,0 +1,290 @@ +attempt($credentials); + * + * @var array> + */ + public array $groups = []; + + /** + * -------------------------------------------------------------------- + * Permissions + * -------------------------------------------------------------------- + * The available permissions in the system. Each system is defined + * where the key is the + * + * If a permission is not listed here it cannot be used. + * + * @var array + */ + public array $permissions = []; + + /** + * -------------------------------------------------------------------- + * Permissions Matrix + * -------------------------------------------------------------------- + * Maps permissions to groups. + * @var array> + */ + public array $matrix = []; + + /** + * @var array> + */ + public array $instanceGroups = []; + + /** + * @var array + */ + public array $instancePermissions = []; + + /** + * @var array> + */ + public array $podcastGroups = []; + + /** + * @var array + */ + public array $podcastPermissions = []; + + /** + * @var string[] + */ + public array $instanceBaseGroups = ['superadmin', 'manager', 'podcaster']; + + /** + * @var string[] + */ + public array $instanceBasePermissions = [ + 'admin.access', + 'admin.settings', + 'plugins.manage', + 'users.manage', + 'persons.manage', + 'pages.manage', + 'podcasts.view', + 'podcasts.create', + 'podcasts.import', + 'fediverse.manage-blocks', + ]; + + /** + * @var array> + */ + public array $instanceMatrix = [ + 'superadmin' => [ + 'admin.*', + 'plugins.*', + 'podcasts.*', + 'users.manage', + 'persons.manage', + 'pages.manage', + 'fediverse.manage-blocks', + ], + 'manager' => ['podcasts.create', 'podcasts.import', 'persons.manage', 'pages.manage'], + 'podcaster' => ['admin.access'], + ]; + + /** + * @var string[] + */ + public array $podcastBaseGroups = ['admin', 'editor', 'author', 'guest']; + + /** + * @var string[] + */ + public array $podcastBasePermissions = [ + 'view', + 'edit', + 'delete', + 'manage-import', + 'manage-persons', + 'manage-subscriptions', + 'manage-contributors', + 'manage-platforms', + 'manage-publications', + 'manage-notifications', + 'interact-as', + 'episodes.view', + 'episodes.create', + 'episodes.edit', + 'episodes.delete', + 'episodes.manage-persons', + 'episodes.manage-clips', + 'episodes.manage-publications', + 'episodes.manage-comments', + ]; + + /** + * @var array + */ + public array $podcastMatrix = [ + 'admin' => ['*'], + 'editor' => [ + 'view', + 'edit', + 'manage-import', + 'manage-persons', + 'manage-platforms', + 'manage-publications', + 'manage-notifications', + 'interact-as', + 'episodes.view', + 'episodes.create', + 'episodes.edit', + 'episodes.delete', + 'episodes.manage-persons', + 'episodes.manage-clips', + 'episodes.manage-publications', + 'episodes.manage-comments', + ], + 'author' => [ + 'view', + 'manage-persons', + 'episodes.view', + 'episodes.create', + 'episodes.edit', + 'episodes.manage-persons', + 'episodes.manage-clips', + ], + 'guest' => ['view', 'episodes.view'], + ]; + + /** + * Fill groups, permissions and matrix based on + */ + public function __construct() + { + parent::__construct(); + + foreach ($this->instanceBaseGroups as $group) { + $this->instanceGroups[$group] = [ + 'title' => lang("Auth.instance_groups.{$group}.title"), + 'description' => lang("Auth.instance_groups.{$group}.description"), + ]; + } + + $this->groups = $this->instanceGroups; + + foreach ($this->instanceBasePermissions as $permission) { + $this->instancePermissions[$permission] = lang("Auth.instance_permissions.{$permission}"); + $this->permissions[$permission] = lang("Auth.instance_permissions.{$permission}"); + } + + $this->matrix = $this->instanceMatrix; + + $this->generateBasePodcastAuthorizations(); + + /** + * For each podcast, include podcast groups, permissions, and matrix into $groups, $permissions, and $matrix + * attributes. + */ + $podcasts = new PodcastModel() + ->findAll(); + foreach ($podcasts as $podcast) { + $this->generatePodcastAuthorizations($podcast->id); + } + } + + public function generateBasePodcastAuthorizations(): void + { + foreach ($this->podcastBaseGroups as $group) { + $this->podcastGroups[$group] = [ + 'title' => lang("Auth.podcast_groups.{$group}.title", [ + 'id' => '{id}', + ]), + 'description' => lang("Auth.podcast_groups.{$group}.description", [ + 'id' => '{id}', + ]), + ]; + } + + foreach ($this->podcastBasePermissions as $permission) { + $this->podcastPermissions[$permission] = lang("Auth.podcast_permissions.{$permission}", [ + 'id' => '{id}', + ]); + $this->permissions[$permission] = lang("Auth.podcast_permissions.{$permission}", [ + 'id' => '{id}', + ]); + } + } + + public function generatePodcastAuthorizations(int $podcastId): void + { + foreach ($this->podcastBaseGroups as $group) { + $podcastGroup = 'podcast#' . $podcastId . '-' . $group; + $this->groups[$podcastGroup] = [ + 'title' => lang("Auth.podcast_groups.{$group}.title", [ + 'id' => $podcastId, + ]), + 'description' => lang("Auth.podcast_groups.{$group}.description", [ + 'id' => $podcastId, + ]), + ]; + } + + foreach ($this->podcastBasePermissions as $permission) { + $podcastPermission = 'podcast#' . $podcastId . '.' . $permission; + $this->permissions[$podcastPermission] = lang("Auth.podcast_permissions.{$permission}", [ + 'id' => $podcastId, + ]); + } + + foreach ($this->podcastMatrix as $group => $permissionWildcards) { + $podcastGroup = 'podcast#' . $podcastId . '-' . $group; + foreach ($permissionWildcards as $permissionWildcard) { + $podcastPermissionWildcard = 'podcast#' . $podcastId . '.' . $permissionWildcard; + $this->matrix[$podcastGroup][] = $podcastPermissionWildcard; + } + } + } +} diff --git a/modules/Auth/Config/AuthRoutes.php b/modules/Auth/Config/AuthRoutes.php new file mode 100644 index 00000000..8e7540a2 --- /dev/null +++ b/modules/Auth/Config/AuthRoutes.php @@ -0,0 +1,45 @@ +>> + */ + public array $routes = [ + 'register' => [ + ['get', 'register', 'RegisterController::registerView', 'register'], + ['post', 'register', 'RegisterController::registerAction'], + ], + 'login' => [ + ['get', 'login', 'LoginController::loginView', 'login'], + ['post', 'login', 'LoginController::loginAction'], + ], + 'magic-link' => [ + [ + 'get', + 'login/magic-link', + 'MagicLinkController::loginView', + 'magic-link', // Route name + ], + ['post', 'login/magic-link', 'MagicLinkController::loginAction'], + [ + 'get', + 'login/verify-magic-link', + 'MagicLinkController::verify', + 'verify-magic-link', // Route name + ], + ], + 'logout' => [['get', 'logout', 'LoginController::logoutAction', 'logout']], + 'auth-actions' => [ + ['get', 'auth/a/show', 'ActionController::show', 'auth-action-show'], + ['post', 'auth/a/handle', 'ActionController::handle', 'auth-action-handle'], + ['post', 'auth/a/verify', 'ActionController::verify', 'auth-action-verify'], + ], + ]; +} diff --git a/modules/Auth/Config/AuthToken.php b/modules/Auth/Config/AuthToken.php new file mode 100644 index 00000000..4f56dc76 --- /dev/null +++ b/modules/Auth/Config/AuthToken.php @@ -0,0 +1,123 @@ + + */ + public array $authenticatorHeader = [ + 'tokens' => 'Authorization', + 'hmac' => 'Authorization', + ]; + + /** + * -------------------------------------------------------------------- + * Unused Token Lifetime + * -------------------------------------------------------------------- + * Determines the amount of time, in seconds, that an unused token can + * be used. + */ + public int $unusedTokenLifetime = YEAR; + + /** + * -------------------------------------------------------------------- + * HMAC secret key byte size + * -------------------------------------------------------------------- + * Specify in integer the desired byte size of the + * HMAC SHA256 byte size + */ + public int $hmacSecretKeyByteSize = 32; + + /** + * -------------------------------------------------------------------- + * HMAC encryption Keys + * -------------------------------------------------------------------- + * This sets the key to be used when encrypting a user's HMAC Secret Key. + * + * 'keys' is an array of keys which will facilitate key rotation. Valid + * keyTitles must include only [a-zA-Z0-9_] and should be kept to a + * max of 8 characters. + * + * Each keyTitle is an associative array containing the required 'key' + * value, and the optional 'driver' and 'digest' values. If the + * 'driver' and 'digest' values are not specified, the default 'driver' + * and 'digest' values will be used. + * + * Old keys will are used to decrypt existing Secret Keys. It is encouraged + * to run 'php spark shield:hmac reencrypt' to update existing Secret + * Key encryptions. + * + * @see https://codeigniter.com/user_guide/libraries/encryption.html + * + * @var array|string + * + * NOTE: The value becomes temporarily a string when setting value as JSON + * from environment variable. + * + * [key_name => ['key' => key_value]] + * or [key_name => ['key' => key_value, 'driver' => driver, 'digest' => digest]] + */ + public $hmacEncryptionKeys = [ + 'k1' => [ + 'key' => '', + ], + ]; + + /** + * -------------------------------------------------------------------- + * HMAC Current Encryption Key Selector + * -------------------------------------------------------------------- + * This specifies which of the encryption keys should be used. + */ + public string $hmacEncryptionCurrentKey = 'k1'; + + /** + * -------------------------------------------------------------------- + * HMAC Encryption Key Driver + * -------------------------------------------------------------------- + * This specifies which of the encryption drivers should be used. + * + * Available drivers: + * - OpenSSL + * - Sodium + */ + public string $hmacEncryptionDefaultDriver = 'OpenSSL'; + + /** + * -------------------------------------------------------------------- + * HMAC Encryption Key Driver + * -------------------------------------------------------------------- + * THis specifies the type of encryption to be used. + * e.g. 'SHA512' or 'SHA256'. + */ + public string $hmacEncryptionDefaultDigest = 'SHA512'; +} diff --git a/modules/Auth/Config/Events.php b/modules/Auth/Config/Events.php new file mode 100644 index 00000000..36b0cf2a --- /dev/null +++ b/modules/Auth/Config/Events.php @@ -0,0 +1,14 @@ +routes($routes); + +// Admin routes for users and podcast contributors $routes->group( - config('Auth') + config('Admin') ->gateway, [ 'namespace' => 'Modules\Auth\Controllers', ], static function ($routes): void { - // Login/out - $routes->get('login', 'AuthController::login', [ - 'as' => 'login', + $routes->get('magic-link-set-password', 'MagicLinkController::setPasswordView', [ + 'as' => 'magic-link-set-password', ]); - $routes->post('login', 'AuthController::attemptLogin'); - $routes->get('logout', 'AuthController::logout', [ - 'as' => 'logout', - ]); - // Registration - $routes->get('register', 'AuthController::register', [ - 'as' => 'register', - ]); - $routes->post('register', 'AuthController::attemptRegister'); - // Activation - $routes->get('activate-account', 'AuthController::activateAccount', [ - 'as' => 'activate-account', - ]); - $routes->get( - 'resend-activate-account', - 'AuthController::resendActivateAccount', - [ - 'as' => 'resend-activate-account', - ], - ); - // Forgot/Resets - $routes->get('forgot', 'AuthController::forgotPassword', [ - 'as' => 'forgot', - ]); - $routes->post('forgot', 'AuthController::attemptForgot'); - $routes->get('reset-password', 'AuthController::resetPassword', [ - 'as' => 'reset-password', - ]); - $routes->post('reset-password', 'AuthController::attemptReset'); - // interacting as an actor - $routes->post('interact-as-actor', 'AuthController::attemptInteractAsActor', [ + $routes->post('magic-link-set-password', 'MagicLinkController::setPasswordAction'); + + $routes->post('interact-as-actor', 'InteractController::interactAsActorAction', [ 'as' => 'interact-as-actor', ]); - } + + // Users + $routes->group('users', static function ($routes): void { + $routes->get('/', 'UserController::list', [ + 'as' => 'user-list', + 'filter' => 'permission:users.manage', + ]); + $routes->get('new', 'UserController::createView', [ + 'as' => 'user-create', + 'filter' => 'permission:users.manage', + ]); + $routes->post('new', 'UserController::createAction', [ + 'filter' => 'permission:users.manage', + ]); + // User + $routes->group('(:num)', static function ($routes): void { + $routes->get('/', 'UserController::view/$1', [ + 'as' => 'user-view', + 'filter' => 'permission:users.manage', + ]); + $routes->get('edit', 'UserController::editView/$1', [ + 'as' => 'user-edit', + 'filter' => 'permission:users.manage', + ]); + $routes->post('edit', 'UserController::editAction/$1', [ + 'filter' => 'permission:users.manage', + ]); + $routes->get('delete', 'UserController::delete/$1', [ + 'as' => 'user-delete', + 'filter' => 'permission:users.manage', + ]); + $routes->post('delete', 'UserController::deleteAction/$1', [ + 'as' => 'user-delete', + 'filter' => 'permission:users.manage', + ]); + }); + }); + // My account + $routes->group('my-account', static function ($routes): void { + $routes->get('/', 'MyAccountController', [ + 'as' => 'my-account', + ]); + $routes->get('change-password', 'MyAccountController::changePassword', [ + 'as' => 'change-password', + ],); + $routes->post('change-password', 'MyAccountController::changeAction'); + }); + + // Podcast contributors + $routes->group('podcasts/(:num)/contributors', static function ($routes): void { + $routes->get('/', 'ContributorController::list/$1', [ + 'as' => 'contributor-list', + 'filter' => 'permission:podcast$1.manage-contributors', + ]); + $routes->get('add', 'ContributorController::createView/$1', [ + 'as' => 'contributor-add', + 'filter' => 'permission:podcast$1.manage-contributors', + ]); + $routes->post( + 'add', + 'ContributorController::createAction/$1', + [ + 'filter' => 'permission:podcast$1.manage-contributors', + ], + ); + // Contributor + $routes->group('(:num)', static function ($routes): void { + $routes->get('/', 'ContributorController::view/$1/$2', [ + 'as' => 'contributor-view', + 'filter' => 'permission:podcast$1.manage-contributors', + ]); + $routes->get( + 'edit', + 'ContributorController::editView/$1/$2', + [ + 'as' => 'contributor-edit', + 'filter' => 'permission:podcast$1.manage-contributors', + ], + ); + $routes->post( + 'edit', + 'ContributorController::editAction/$1/$2', + [ + 'filter' => 'permission:podcast$1.manage-contributors', + ], + ); + $routes->get( + 'remove', + 'ContributorController::removeView/$1/$2', + [ + 'as' => 'contributor-remove', + 'filter' => 'permission:podcast$1.manage-contributors', + ], + ); + $routes->post( + 'remove', + 'ContributorController::removeAction/$1/$2', + [ + 'filter' => 'permission:podcast$1.manage-contributors', + ], + ); + }); + }); + }, ); diff --git a/modules/Auth/Config/Services.php b/modules/Auth/Config/Services.php index 71b8129c..1ef66bae 100644 --- a/modules/Auth/Config/Services.php +++ b/modules/Auth/Config/Services.php @@ -4,87 +4,23 @@ declare(strict_types=1); namespace Modules\Auth\Config; -use App\Models\UserModel; -use CodeIgniter\Config\BaseService; -use CodeIgniter\Model; -use Modules\Auth\Authorization\FlatAuthorization; -use Modules\Auth\Authorization\GroupModel; -use Modules\Auth\Authorization\PermissionModel; -use Myth\Auth\Models\LoginModel; +use Config\Services as BaseService; +use Modules\Auth\Auth; -/** - * Services Configuration file. - * - * Services are simply other classes/libraries that the system uses to do its job. This is used by CodeIgniter to allow - * the core of the framework to be swapped out easily without affecting the usage within the rest of your application. - * - * This file holds any application-specific services, or service overrides that you might need. An example has been - * included with the general method format you should use for your service methods. For more examples, see the core - * Services file at system/Config/Services.php. - */ class Services extends BaseService { /** - * @return mixed + * The base auth class */ - public static function authentication( - string $lib = 'local', - Model $userModel = null, - Model $loginModel = null, - bool $getShared = true - ) { + public static function auth(bool $getShared = true): Auth + { if ($getShared) { - return self::getSharedInstance('authentication', $lib, $userModel, $loginModel); + /** @var Auth */ + return self::getSharedInstance('auth'); } - // config() checks first in app/Config $config = config('Auth'); - $class = $config->authenticationLibs[$lib]; - - $instance = new $class($config); - - if ($userModel === null) { - $userModel = new UserModel(); - } - - if ($loginModel === null) { - $loginModel = new LoginModel(); - } - - return $instance->setUserModel($userModel) - ->setLoginModel($loginModel); - } - - /** - * @return mixed|$this - */ - public static function authorization( - Model $groupModel = null, - Model $permissionModel = null, - Model $userModel = null, - bool $getShared = true - ) { - if ($getShared) { - return self::getSharedInstance('authorization', $groupModel, $permissionModel, $userModel); - } - - if ($groupModel === null) { - $groupModel = new GroupModel(); - } - - if ($permissionModel === null) { - $permissionModel = new PermissionModel(); - } - - /* @phpstan-ignore-next-line */ - $instance = new FlatAuthorization($groupModel, $permissionModel); - - if ($userModel === null) { - $userModel = new UserModel(); - } - - /* @phpstan-ignore-next-line */ - return $instance->setUserModel($userModel); + return new Auth($config); } } diff --git a/modules/Auth/Controllers/ActionController.php b/modules/Auth/Controllers/ActionController.php new file mode 100644 index 00000000..649005ee --- /dev/null +++ b/modules/Auth/Controllers/ActionController.php @@ -0,0 +1,29 @@ +config->allowRegistration) { - return redirect() - ->back() - ->withInput() - ->with('error', lang('Auth.registerDisabled')); - } - - $users = model('UserModel'); - - // Validate here first, since some things, - // like the password, can only be validated properly here. - $rules = [ - 'username' => - 'required|alpha_numeric_space|min_length[3]|is_unique[users.username]', - 'email' => 'required|valid_email|is_unique[users.email]', - 'password' => 'required|strong_password', - ]; - - if (! $this->validate($rules)) { - return redirect() - ->back() - ->withInput() - ->with('errors', service('validation')->getErrors()); - } - - // Save the user - $allowedPostFields = array_merge(['password'], $this->config->validFields, $this->config->personalFields); - $user = new User($this->request->getPost($allowedPostFields)); - - $this->config->requireActivation === null - ? $user->activate() - : $user->generateActivateHash(); - - // Ensure default group gets assigned if set - if ($this->config->defaultUserGroup !== null) { - $users = $users->withGroup($this->config->defaultUserGroup); - } - - if (! $users->save($user)) { - return redirect() - ->back() - ->withInput() - ->with('errors', $users->errors()); - } - - if ($this->config->requireActivation !== null) { - $activator = service('activator'); - $sent = $activator->send($user); - - if (! $sent) { - return redirect() - ->back() - ->withInput() - ->with('error', $activator->error() ?? lang('Auth.unknownError')); - } - - // Success! - return redirect() - ->route('login') - ->with('message', lang('Auth.activationSuccess')); - } - - // Success! - return redirect() - ->route('login') - ->with('message', lang('Auth.registerSuccess')); - } - - /** - * Verifies the code with the email and saves the new password, if they all pass validation. - */ - public function attemptReset(): RedirectResponse - { - if ($this->config->activeResetter === null) { - return redirect() - ->route('login') - ->with('error', lang('Auth.forgotDisabled')); - } - - $users = model('UserModel'); - - // First things first - log the reset attempt. - $users->logResetAttempt( - $this->request->getPost('email'), - $this->request->getPost('token'), - $this->request->getIPAddress(), - (string) $this->request->getUserAgent(), - ); - - $rules = [ - 'token' => 'required', - 'email' => 'required|valid_email', - 'password' => 'required|strong_password', - ]; - - if (! $this->validate($rules)) { - return redirect() - ->back() - ->withInput() - ->with('errors', $users->errors()); - } - - $user = $users - ->where('email', $this->request->getPost('email')) - ->where('reset_hash', $this->request->getPost('token')) - ->first(); - - if ($user === null) { - return redirect() - ->back() - ->with('error', lang('Auth.forgotNoUser')); - } - - // Reset token still valid? - if ( - $user->reset_expires !== null && - time() > $user->reset_expires->getTimestamp() - ) { - return redirect() - ->back() - ->withInput() - ->with('error', lang('Auth.resetTokenExpired')); - } - - // Success! Save the new password, and cleanup the reset hash. - $user->password = $this->request->getPost('password'); - $user->reset_hash = null; - $user->reset_at = date('Y-m-d H:i:s'); - $user->reset_expires = null; - $user->force_pass_reset = false; - $users->save($user); - - helper('auth'); - - // set interact_as_actor_id value - $userPodcasts = $user->podcasts; - if ($userPodcasts = $user->podcasts) { - set_interact_as_actor($userPodcasts[0]->actor_id); - } - - return redirect() - ->route('login') - ->with('message', lang('Auth.resetSuccess')); - } - - public function attemptInteractAsActor(): RedirectResponse - { - $rules = [ - 'actor_id' => 'required|numeric', - ]; - - if (! $this->validate($rules)) { - return redirect() - ->back() - ->withInput() - ->with('errors', service('validation')->getErrors()); - } - - helper('auth'); - - set_interact_as_actor((int) $this->request->getPost('actor_id')); - - return redirect()->back(); - } -} diff --git a/modules/Auth/Controllers/ContributorController.php b/modules/Auth/Controllers/ContributorController.php new file mode 100644 index 00000000..119703bf --- /dev/null +++ b/modules/Auth/Controllers/ContributorController.php @@ -0,0 +1,266 @@ +getPodcastById((int) $params[0])) instanceof Podcast) { + throw PageNotFoundException::forPageNotFound(); + } + + $this->podcast = $podcast; + + if (count($params) <= 1) { + return $this->{$method}(); + } + + if (($this->contributor = new UserModel()->getPodcastContributor( + (int) $params[1], + (int) $params[0], + )) instanceof User) { + return $this->{$method}(); + } + + throw PageNotFoundException::forPageNotFound(); + } + + public function list(): string + { + $data = [ + 'podcast' => $this->podcast, + ]; + + $this->setHtmlHead(lang('Contributor.podcast_contributors')); + replace_breadcrumb_params([ + 0 => $this->podcast->at_handle, + ]); + return view('contributor/list', $data); + } + + public function view(): string + { + $data = [ + 'podcast' => $this->podcast, + 'contributor' => new UserModel() + ->getPodcastContributor($this->contributor->id, $this->podcast->id), + ]; + + $this->setHtmlHead(lang('Contributor.view', [ + 'username' => esc($this->contributor->username), + 'podcastTitle' => esc($this->podcast->title), + ])); + replace_breadcrumb_params([ + 0 => $this->podcast->at_handle, + 1 => $this->contributor->username, + ]); + return view('contributor/view', $data); + } + + public function createView(): string + { + helper('form'); + + $users = new UserModel() + ->findAll(); + $contributorOptions = array_reduce( + $users, + static function (array $result, User $user): array { + $result[] = [ + 'value' => $user->id, + 'label' => $user->username, + ]; + return $result; + }, + [], + ); + + $roles = setting('AuthGroups.podcastBaseGroups'); + $roleOptions = []; + array_walk( + $roles, + static function (string $role, $key) use (&$roleOptions): array { + $roleOptions[] = [ + 'value' => $role, + 'label' => lang('Auth.podcast_groups.' . $role . '.title'), + ]; + return $roleOptions; + }, + [], + ); + + $data = [ + 'podcast' => $this->podcast, + 'contributorOptions' => $contributorOptions, + 'roleOptions' => $roleOptions, + ]; + + $this->setHtmlHead(lang('Contributor.add_contributor', [esc($this->podcast->title)])); + replace_breadcrumb_params([ + 0 => $this->podcast->at_handle, + ]); + return view('contributor/create', $data); + } + + public function createAction(): RedirectResponse + { + /** @var User $user */ + $user = new UserModel() + ->find((int) $this->request->getPost('user')); + + if (get_podcast_group($user, $this->podcast->id)) { + return redirect() + ->back() + ->withInput() + ->with('errors', [lang('Contributor.messages.alreadyAddedError')]); + } + + add_podcast_group($user, $this->podcast->id, $this->request->getPost('role')); + + return redirect()->route('contributor-list', [$this->podcast->id]); + } + + public function editView(): string|RedirectResponse + { + helper('form'); + + $roles = setting('AuthGroups.podcastBaseGroups'); + $roleOptions = []; + array_walk( + $roles, + static function (string $role) use (&$roleOptions): array { + $roleOptions[] = [ + 'value' => $role, + 'label' => lang('Auth.podcast_groups.' . $role . '.title'), + ]; + return $roleOptions; + }, + [], + ); + + $contributorGroup = get_podcast_group($this->contributor, $this->podcast->id); + + if ($contributorGroup === null) { + return redirect() + ->back() + ->withInput() + ->with('errors', [lang('Contributor.messages.notAddedError')]); + } + + $data = [ + 'podcast' => $this->podcast, + 'contributor' => $this->contributor, + 'contributorGroup' => $contributorGroup, + 'roleOptions' => $roleOptions, + ]; + + $this->setHtmlHead(lang('Contributor.edit_role', [esc($this->contributor->username)])); + replace_breadcrumb_params([ + 0 => $this->podcast->at_handle, + 1 => $this->contributor->username, + ]); + return view('contributor/edit', $data); + } + + public function editAction(): RedirectResponse + { + // forbid updating a podcast owner + if ($this->podcast->created_by === $this->contributor->id) { + return redirect() + ->back() + ->with('errors', [lang('Contributor.messages.editOwnerError')]); + } + + $group = $this->request->getPost('role'); + + set_podcast_group($this->contributor, $this->podcast->id, $group); + + cache() + ->delete("podcast#{$this->podcast->id}_contributors"); + + return redirect()->route('contributor-list', [$this->podcast->id])->with( + 'message', + lang('Contributor.messages.editSuccess'), + ); + } + + public function removeView(): string + { + helper('form'); + + $data = [ + 'podcast' => $this->podcast, + 'contributor' => $this->contributor, + ]; + + $this->setHtmlHead(lang('Contributor.delete_form.title', [ + 'contributor' => $this->contributor->username, + ])); + replace_breadcrumb_params([ + 0 => $this->podcast->at_handle, + 1 => $this->contributor->username, + ]); + return view('contributor/remove', $data); + } + + public function removeAction(): RedirectResponse + { + if ($this->podcast->created_by === $this->contributor->id) { + return redirect() + ->back() + ->with('errors', [lang('Contributor.messages.removeOwnerError')]); + } + + $rules = [ + 'understand' => 'required', + ]; + + if (! $this->validate($rules)) { + return redirect() + ->back() + ->withInput() + ->with('errors', $this->validator->getErrors()); + } + + cache() + ->delete("podcast#{$this->podcast->id}_contributors"); + + // remove contributor from podcast group + $this->contributor->removeGroup(get_podcast_group($this->contributor, $this->podcast->id, false)); + + return redirect() + ->route('contributor-list', [$this->podcast->id]) + ->with( + 'message', + lang('Contributor.messages.removeSuccess', [ + 'username' => $this->contributor->username, + 'podcastTitle' => $this->podcast->title, + ]), + ); + } +} diff --git a/modules/Auth/Controllers/InteractController.php b/modules/Auth/Controllers/InteractController.php new file mode 100644 index 00000000..d8281b62 --- /dev/null +++ b/modules/Auth/Controllers/InteractController.php @@ -0,0 +1,36 @@ + 'required|numeric', + ]; + + if (! $this->validate($rules)) { + return redirect() + ->back() + ->withInput() + ->with('errors', $this->validator->getErrors()); + } + + $validData = $this->validator->getValidated(); + + helper('auth'); + + set_interact_as_actor((int) $validData['actor_id']); + + return redirect()->back(); + } +} diff --git a/modules/Auth/Controllers/LoginController.php b/modules/Auth/Controllers/LoginController.php new file mode 100644 index 00000000..d4d7386e --- /dev/null +++ b/modules/Auth/Controllers/LoginController.php @@ -0,0 +1,26 @@ +to(config('Auth')->loginRedirect()); + } + + return view(setting('Auth.views')['magic-link-set-password']); + } + + public function setPasswordAction(): RedirectResponse + { + $rules = [ + 'new_password' => 'required|strong_password', + ]; + + if (! $this->validate($rules)) { + return redirect() + ->back() + ->withInput() + ->with('errors', $this->validator->getErrors()); + } + + $validData = $this->validator->getValidated(); + + $user = auth() + ->user(); + + if ($user instanceof User) { + // set new password to user + $user->password = $validData['new_password']; + + $userModel = auth() + ->getProvider(); + $userModel->save($user); + } + + // remove magic login session to reinstate normal check + if (session('magicLogin')) { + session()->removeTempdata('magicLogin'); + } + + // Success! + return redirect()->to(config('Auth')->loginRedirect()) + ->with('message', lang('MyAccount.messages.passwordChangeSuccess')); + } +} diff --git a/modules/Admin/Controllers/MyAccountController.php b/modules/Auth/Controllers/MyAccountController.php similarity index 50% rename from modules/Admin/Controllers/MyAccountController.php rename to modules/Auth/Controllers/MyAccountController.php index 8ffa0247..844b0112 100644 --- a/modules/Admin/Controllers/MyAccountController.php +++ b/modules/Auth/Controllers/MyAccountController.php @@ -8,16 +8,17 @@ declare(strict_types=1); * @link https://castopod.org/ */ -namespace Modules\Admin\Controllers; +namespace Modules\Auth\Controllers; -use App\Models\UserModel; use CodeIgniter\HTTP\RedirectResponse; -use Config\Services; +use CodeIgniter\Shield\Entities\User; +use Modules\Admin\Controllers\BaseController; class MyAccountController extends BaseController { public function index(): string { + $this->setHtmlHead(lang('MyAccount.info')); return view('my_account/view'); } @@ -25,18 +26,14 @@ class MyAccountController extends BaseController { helper('form'); + $this->setHtmlHead(lang('MyAccount.changePassword')); return view('my_account/change_password'); } - public function attemptChange(): RedirectResponse + public function changeAction(): RedirectResponse { - $auth = Services::authentication(); - $userModel = new UserModel(); - - // Validate here first, since some things, - // like the password, can only be validated properly here. $rules = [ - 'password' => 'required', + 'password' => 'required', 'new_password' => 'required|strong_password|differs[password]', ]; @@ -44,30 +41,37 @@ class MyAccountController extends BaseController return redirect() ->back() ->withInput() - ->with('errors', $userModel->errors()); + ->with('errors', $this->validator->getErrors()); } + $validData = $this->validator->getValidated(); + + // check credentials with the old password if logged in without magic link $credentials = [ - 'email' => user() + 'email' => auth() + ->user() ->email, - 'password' => $this->request->getPost('password'), + 'password' => $validData['password'], ]; - if (! $auth->validate($credentials)) { - return redirect() - ->back() - ->withInput() + $validCreds = auth() + ->check($credentials); + + if (! $validCreds->isOK()) { + return redirect()->back() ->with('error', lang('MyAccount.messages.wrongPasswordError')); } - user() - ->password = $this->request->getPost('new_password'); + $user = auth() + ->user(); - if (! $userModel->update(user_id(), user())) { - return redirect() - ->back() - ->withInput() - ->with('errors', $userModel->errors()); + if ($user instanceof User) { + // set new password to user + $user->password = $validData['new_password']; + + $userModel = auth() + ->getProvider(); + $userModel->save($user); } // Success! diff --git a/modules/Auth/Controllers/RegisterController.php b/modules/Auth/Controllers/RegisterController.php new file mode 100644 index 00000000..4a093f95 --- /dev/null +++ b/modules/Auth/Controllers/RegisterController.php @@ -0,0 +1,29 @@ +{$method}(); + } + + if (($this->user = new UserModel()->find($params[0])) instanceof User) { + return $this->{$method}(); + } + + throw PageNotFoundException::forPageNotFound(); + } + + public function list(): string + { + $data = [ + 'users' => new UserModel() + ->findAll(), + ]; + + $this->setHtmlHead(lang('User.all_users')); + return view('user/list', $data); + } + + public function view(): string + { + $data = [ + 'user' => $this->user, + ]; + + $this->setHtmlHead(lang('User.view', [ + 'username' => esc($this->user->username), + ])); + replace_breadcrumb_params([ + 0 => $this->user->username, + ]); + return view('user/view', $data); + } + + public function createView(): string + { + helper('form'); + + $roles = setting('AuthGroups.instanceGroups'); + $roleOptions = []; + array_walk( + $roles, + static function (array $role, $key) use (&$roleOptions): array { + $roleOptions[] = [ + 'value' => $key, + 'label' => $role['title'], + ]; + return $roleOptions; + }, + [], + ); + + $data = [ + 'roleOptions' => $roleOptions, + ]; + + $this->setHtmlHead(lang('User.create')); + return view('user/create', $data); + } + + /** + * Create the user with the provided username and email. The password is set as a random string and a magic link is + * sent to the user to allow them setting their password. + */ + public function createAction(): RedirectResponse + { + helper(['text', 'email']); + + $db = db_connect(); + $db->transStart(); + + $userModel = new UserModel(); + + // Save the user + $email = $this->request->getPost('email'); + $user = new User([ + 'username' => $this->request->getPost('username'), + 'email' => $email, + // set a random password + // user will be prompted to change it on first magic link login. + 'password' => random_string('alnum', 32), + ]); + try { + $userModel->save($user); + } catch (ValidationException) { + return redirect()->back() + ->withInput() + ->with('errors', $userModel->errors()); + } + + $user = $userModel->findById($userModel->getInsertID()); + $user->addGroup($this->request->getPost('role')); + + // **** SEND WELCOME LINK FOR FIRST LOGIN **** + + $identityModel = model('UserIdentityModel'); + + // Delete any previous magic-link identities + $identityModel->deleteIdentitiesByType($user, Session::ID_TYPE_MAGIC_LINK); + + // Generate the code and save it as an identity + $token = random_string('crypto', 20); + + $identityModel->insert([ + 'user_id' => $user->id, + 'type' => Session::ID_TYPE_MAGIC_LINK, + 'secret' => $token, + 'expires' => Time::now()->addSeconds(setting('Auth.welcomeLinkLifetime'))->format('Y-m-d H:i:s'), + ]); + + // Send the user an email with the code + $email = emailer() + ->setFrom(setting('Email.fromEmail'), setting('Email.fromName') ?? ''); + $email->setTo($user->email); + $email->setSubject(lang('Auth.welcomeSubject', [ + 'siteName' => setting('App.siteName'), + ])); + $email->setMessage(view(setting('Auth.views')['welcome-email'], [ + 'token' => $token, + ], [ + 'theme' => 'auth', + ])); + + if (! $email->send(false)) { + log_message('error', $email->printDebugger(['headers'])); + + return redirect()->back() + ->with('error', lang('Auth.unableSendEmailToUser', [$user->email])); + } + + // Clear the email + $email->clear(); + + $db->transComplete(); + + // Success! + return redirect() + ->route('user-list') + ->with('message', lang('User.messages.createSuccess', [ + 'username' => $user->username, + ])); + } + + public function editView(): string + { + helper('form'); + + $roles = setting('AuthGroups.instanceGroups'); + $roleOptions = []; + array_walk( + $roles, + static function (array $role, $key) use (&$roleOptions): array { + $roleOptions[] = [ + 'value' => $key, + 'label' => $role['title'], + ]; + return $roleOptions; + }, + [], + ); + + $data = [ + 'user' => $this->user, + 'roleOptions' => $roleOptions, + ]; + + $this->setHtmlHead(lang('User.edit_role', [ + 'username' => esc($this->user->username), + ])); + replace_breadcrumb_params([ + 0 => $this->user->username, + ]); + return view('user/edit', $data); + } + + public function editAction(): RedirectResponse + { + // The instance owner is a superadmin and the only user that cannot be demoted. + if ((bool) $this->user->is_owner) { + return redirect() + ->back() + ->with('errors', [ + lang('User.messages.editOwnerError', [ + 'username' => $this->user->username, + ]), + ]); + } + + $group = $this->request->getPost('role'); + + set_instance_group($this->user, $group); + + // Success! + return redirect() + ->route('user-list') + ->with('message', lang('User.messages.roleEditSuccess', [ + 'username' => $this->user->username, + ])); + } + + public function deleteView(): string + { + helper(['form']); + + $data = [ + 'user' => $this->user, + ]; + + $this->setHtmlHead(lang('User.delete_form.title', [ + 'user' => $this->user->username, + ])); + replace_breadcrumb_params([ + 0 => $this->user->username, + ]); + return view('user/delete', $data); + } + + public function deleteAction(): RedirectResponse + { + // You cannot delete the instance owner. + if ((bool) $this->user->is_owner) { + return redirect() + ->back() + ->with('errors', [ + lang('User.messages.deleteOwnerError', [ + 'username' => $this->user->username, + ]), + ]); + } + + // You cannot delete a superadmin + // superadmin has to be demoted before being deleted + if ($this->user->inGroup(setting('AuthGroups.mostPowerfulPodcastGroup'))) { + return redirect() + ->back() + ->with('errors', [ + lang('User.messages.deleteSuperAdminError', [ + 'username' => $this->user->username, + ]), + ]); + } + + $rules = [ + 'understand' => 'required', + ]; + + if (! $this->validate($rules)) { + return redirect() + ->back() + ->withInput() + ->with('errors', $this->validator->getErrors()); + } + + new UserModel() + ->delete($this->user->id, true); + + return redirect() + ->route('user-list') + ->with('message', lang('User.messages.deleteSuccess', [ + 'username' => $this->user->username, + ])); + } +} diff --git a/modules/Auth/Database/Migrations/2020-07-03-191500_add_podcasts_users.php b/modules/Auth/Database/Migrations/2020-07-03-191500_add_podcasts_users.php deleted file mode 100644 index b6535c4a..00000000 --- a/modules/Auth/Database/Migrations/2020-07-03-191500_add_podcasts_users.php +++ /dev/null @@ -1,46 +0,0 @@ -forge->addField([ - 'podcast_id' => [ - 'type' => 'INT', - 'unsigned' => true, - ], - 'user_id' => [ - 'type' => 'INT', - 'unsigned' => true, - ], - 'group_id' => [ - 'type' => 'INT', - 'unsigned' => true, - ], - ]); - $this->forge->addPrimaryKey(['user_id', 'podcast_id']); - $this->forge->addForeignKey('user_id', 'users', 'id', '', 'CASCADE'); - $this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE'); - $this->forge->addForeignKey('group_id', 'auth_groups', 'id', '', 'CASCADE'); - $this->forge->createTable('podcasts_users'); - } - - public function down(): void - { - $this->forge->dropTable('podcasts_users'); - } -} diff --git a/modules/Auth/Database/Migrations/2020-12-29-100000_add_is_owner_to_users.php b/modules/Auth/Database/Migrations/2020-12-29-100000_add_is_owner_to_users.php new file mode 100644 index 00000000..7f0badee --- /dev/null +++ b/modules/Auth/Database/Migrations/2020-12-29-100000_add_is_owner_to_users.php @@ -0,0 +1,32 @@ + [ + 'type' => 'TINYINT', + 'constraint' => 1, + 'default' => 0, + 'null' => false, + ], + ]; + + $this->forge->addColumn('users', $fields); + } + + #[Override] + public function down(): void + { + $fields = ['is_owner']; + $this->forge->dropColumn('users', $fields); + } +} diff --git a/modules/Auth/Database/Seeds/AuthSeeder.php b/modules/Auth/Database/Seeds/AuthSeeder.php deleted file mode 100644 index 976c904b..00000000 --- a/modules/Auth/Database/Seeds/AuthSeeder.php +++ /dev/null @@ -1,302 +0,0 @@ -[] - */ - protected array $groups = [ - [ - 'name' => 'superadmin', - 'description' => - 'Somebody who has access to all the castopod instance features', - ], - [ - 'name' => 'podcast_admin', - 'description' => - 'Somebody who has access to all the features within a given podcast', - ], - ]; - - /** - * Build permissions array as a list of: - * - * ``` context => [ [action, description], [action, description], ... ] ``` - * - * @var array[]> - */ - protected array $permissions = [ - 'users' => [ - [ - 'name' => 'create', - 'description' => 'Create a user', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'list', - 'description' => 'List all users', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'view', - 'description' => 'View any user info', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'manage_authorizations', - 'description' => 'Add or remove roles/permissions to a user', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'manage_bans', - 'description' => 'Ban / unban a user', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'force_pass_reset', - 'description' => - 'Force a user to update his password upon next login', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'delete', - 'description' => - 'Delete user without removing him from database', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'delete_permanently', - 'description' => - 'Delete all occurrences of a user from the database', - 'has_permission' => ['superadmin'], - ], - ], - 'pages' => [ - [ - 'name' => 'manage', - 'description' => 'List / create / edit / delete pages', - 'has_permission' => ['superadmin'], - ], - ], - 'podcasts' => [ - [ - 'name' => 'create', - 'description' => 'Add a new podcast', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'import', - 'description' => 'Import a new podcast from an external feed', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'list', - 'description' => 'List all podcasts and their episodes', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'view', - 'description' => 'View any podcast and their contributors list', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'delete', - 'description' => 'Delete any podcast from the database', - 'has_permission' => ['superadmin'], - ], - ], - 'episodes' => [ - [ - 'name' => 'list', - 'description' => 'List all episodes of any podcast', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'view', - 'description' => 'View any episode of any podcast', - 'has_permission' => ['superadmin'], - ], - ], - 'podcast' => [ - [ - 'name' => 'view', - 'description' => 'View a podcast', - 'has_permission' => ['podcast_admin'], - ], - [ - 'name' => 'edit', - 'description' => 'Edit a podcast', - 'has_permission' => ['podcast_admin'], - ], - [ - 'name' => 'manage_contributors', - 'description' => - 'Add / remove contributors to a podcast and edit their roles', - 'has_permission' => ['podcast_admin'], - ], - [ - 'name' => 'manage_platforms', - 'description' => 'Set / remove platform links of a podcast', - 'has_permission' => ['podcast_admin'], - ], - [ - 'name' => 'manage_publications', - 'description' => - 'Publish / unpublish episodes & posts of a podcast', - 'has_permission' => ['podcast_admin'], - ], - [ - 'name' => 'interact_as', - 'description' => - 'Interact as the podcast to favourite / share or reply to posts.', - 'has_permission' => ['podcast_admin'], - ], - ], - 'podcast_episodes' => [ - [ - 'name' => 'list', - 'description' => 'List all episodes of a podcast', - 'has_permission' => ['podcast_admin'], - ], - [ - 'name' => 'view', - 'description' => 'View any episode of a podcast', - 'has_permission' => ['podcast_admin'], - ], - [ - 'name' => 'create', - 'description' => 'Add new episodes for a podcast', - 'has_permission' => ['podcast_admin'], - ], - [ - 'name' => 'edit', - 'description' => 'Edit an episode of a podcast', - 'has_permission' => ['podcast_admin'], - ], - [ - 'name' => 'delete', - 'description' => - 'Delete all occurrences of an episode of a podcast from the database', - 'has_permission' => ['podcast_admin'], - ], - ], - 'person' => [ - [ - 'name' => 'create', - 'description' => 'Add a new person', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'list', - 'description' => 'List all persons', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'view', - 'description' => 'View any person', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'edit', - 'description' => 'Edit a person', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'delete', - 'description' => - 'Delete permanently any person from the database', - 'has_permission' => ['superadmin'], - ], - ], - 'fediverse' => [ - [ - 'name' => 'block_actors', - 'description' => - 'Block fediverse actors from interacting with the instance.', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'block_domains', - 'description' => - 'Block fediverse domains from interacting with the instance.', - 'has_permission' => ['superadmin'], - ], - ], - ]; - - public function run(): void - { - $groupId = 0; - $dataGroups = []; - foreach ($this->groups as $group) { - $dataGroups[] = [ - 'id' => ++$groupId, - 'name' => $group['name'], - 'description' => $group['description'], - ]; - } - - // Map permissions to a format the `auth_permissions` table expects - $dataPermissions = []; - $dataGroupsPermissions = []; - $permissionId = 0; - foreach ($this->permissions as $context => $actions) { - foreach ($actions as $action) { - $dataPermissions[] = [ - 'id' => ++$permissionId, - 'name' => $context . '-' . $action['name'], - 'description' => $action['description'], - ]; - - foreach ($action['has_permission'] as $role) { - // link permission to specified groups - $dataGroupsPermissions[] = [ - 'group_id' => $this->getGroupIdByName($role, $dataGroups), - 'permission_id' => $permissionId, - ]; - } - } - } - - $this->db - ->table('auth_permissions') - ->ignore(true) - ->insertBatch($dataPermissions); - $this->db - ->table('auth_groups') - ->ignore(true) - ->insertBatch($dataGroups); - $this->db - ->table('auth_groups_permissions') - ->ignore(true) - ->insertBatch($dataGroupsPermissions); - } - - /** - * @param array[] $dataGroups - */ - public static function getGroupIdByName(string $name, array $dataGroups): ?int - { - foreach ($dataGroups as $group) { - if ($group['name'] === $name) { - return $group['id']; - } - } - - return null; - } -} diff --git a/modules/Auth/Entities/User.php b/modules/Auth/Entities/User.php deleted file mode 100644 index 312bde68..00000000 --- a/modules/Auth/Entities/User.php +++ /dev/null @@ -1,109 +0,0 @@ - - */ - protected $casts = [ - 'id' => 'integer', - 'active' => 'boolean', - 'force_pass_reset' => 'boolean', - 'podcast_id' => '?integer', - 'podcast_role' => '?string', - ]; - - public function getIsOwner(): bool - { - $firstUser = (new UserModel())->first(); - - if (! $firstUser instanceof self) { - return false; - } - - return $this->username === $firstUser->username; - } - - /** - * Returns the podcasts the user is contributing to - * - * @return Podcast[] - */ - public function getPodcasts(): array - { - if ($this->id === null) { - throw new RuntimeException('Users must be created before getting podcasts.'); - } - - if ($this->podcasts === null) { - $this->podcasts = (new PodcastModel())->getUserPodcasts($this->id); - } - - return $this->podcasts; - } - - /** - * Returns the ids of the user's actors that have unread notifications - * - * @return int[] - */ - public function getActorIdsWithUnreadNotifications(): array - { - if ($this->getPodcasts() === []) { - return []; - } - - $unreadNotifications = (new NotificationModel())->whereIn( - 'target_actor_id', - array_column($this->getPodcasts(), 'actor_id') - ) - ->where('read_at', null) - ->findAll(); - - return array_column($unreadNotifications, 'target_actor_id'); - } -} diff --git a/modules/Auth/Filters/PermissionFilter.php b/modules/Auth/Filters/PermissionFilter.php index 19824798..982e9f64 100644 --- a/modules/Auth/Filters/PermissionFilter.php +++ b/modules/Auth/Filters/PermissionFilter.php @@ -4,96 +4,105 @@ declare(strict_types=1); namespace Modules\Auth\Filters; +use App\Entities\Podcast; use App\Models\PodcastModel; use CodeIgniter\Filters\FilterInterface; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; -use Config\Services; -use Myth\Auth\Exceptions\PermissionException; +use Override; +use RuntimeException; +/** + * Permission Authorization Filter. + */ class PermissionFilter implements FilterInterface { /** - * Do whatever processing this filter needs to do. By default it should not return anything during normal execution. - * However, when an abnormal state is found, it should return an instance of CodeIgniter\HTTP\Response. If it does, - * script execution will end and that Response will be sent back to the client, allowing for error pages, redirects, - * etc. + * @param string[]|null $arguments * - * @param string[]|null $params - * @return void|mixed + * @return RequestInterface|ResponseInterface|string|null */ - public function before(RequestInterface $request, $params = null) + #[Override] + public function before(RequestInterface $request, $arguments = null) { - helper('auth'); - - if ($params === null) { - return; + if ($arguments === null || $arguments === []) { + return null; } - $authenticate = Services::authentication(); - - // if no user is logged in then send to the login form - if (! $authenticate->check()) { - session()->set('redirect_url', current_url()); - return redirect('login'); + if (! auth()->loggedIn()) { + return redirect()->route('login'); } - helper('misc'); - $authorize = Services::authorization(); - $router = Services::router(); - $routerParams = $router->params(); - $result = false; - - // Check if user has at least one of the permissions - foreach ($params as $permission) { - // check if permission is for a specific podcast - if ( - (str_starts_with($permission, 'podcast-') || - str_starts_with($permission, 'podcast_episodes-')) && - $routerParams !== [] - ) { - if ( - ($groupId = (new PodcastModel())->getContributorGroupId( - $authenticate->id(), - $routerParams[0], - )) && - $authorize->groupHasPermission($permission, $groupId) - ) { - $result = true; - break; - } - } elseif ( - $authorize->hasPermission($permission, $authenticate->id()) - ) { - $result = true; - break; - } + if ($this->isAuthorized($arguments)) { + return null; } - if (! $result) { - if ($authenticate->silent()) { - $redirectURL = session('redirect_url') ?? '/'; - unset($_SESSION['redirect_url']); - return redirect() - ->to($redirectURL) - ->with('error', lang('Auth.notEnoughPrivilege')); - } - - throw new PermissionException(lang('Auth.notEnoughPrivilege')); - } + throw new RuntimeException(lang('Auth.notEnoughPrivilege'), 403); } - //-------------------------------------------------------------------- - /** - * Allows After filters to inspect and modify the response object as needed. This method does not allow any way to - * stop execution of other after filters, short of throwing an Exception or Error. + * @param string[]|null $arguments * - * @param string[]|null $arguments + * @return ResponseInterface|null */ - public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void + #[Override] + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { + return null; } - //-------------------------------------------------------------------- + /** + * Ensures the user is logged in and has one or more + * of the permissions as specified in the filter. + * + * @param string[] $arguments + */ + protected function isAuthorized(array $arguments): bool + { + $result = true; + + foreach ($arguments as $permission) { + // is permission specific to a podcast? + if (str_contains($permission, '$')) { + $router = service('router'); + $routerParams = $router->params(); + + if (! preg_match('/\$(\d+)\./', $permission, $match)) { + throw new RuntimeException(sprintf( + 'Could not get podcast identifier from permission %s', + $permission, + ), 1); + } + + $paramKey = ((int) $match[1]) - 1; + if (! array_key_exists($paramKey, $routerParams)) { + throw new RuntimeException(sprintf('Router param does not exist at key %s', $match[1]), 1); + } + + $podcastParam = $routerParams[$paramKey]; + + // get podcast id + $podcastId = null; + if (is_numeric($podcastParam)) { + $podcastId = (int) $podcastParam; + } else { + $podcast = new PodcastModel() + ->getPodcastByHandle($podcastParam); + if ($podcast instanceof Podcast) { + $podcastId = $podcast->id; + } + } + + if ($podcastId !== null) { + $permission = str_replace('$' . $match[1], '#' . $podcastId, $permission); + } + } + + $result = $result && auth() + ->user() + ->can($permission); + } + + return $result; + } } diff --git a/modules/Auth/Helpers/auth_helper.php b/modules/Auth/Helpers/auth_helper.php new file mode 100644 index 00000000..36b6a075 --- /dev/null +++ b/modules/Auth/Helpers/auth_helper.php @@ -0,0 +1,305 @@ +setAuthenticator($alias); + } +} + +if (! function_exists('set_interact_as_actor')) { + /** + * Sets the actor id of which the user is acting as + */ + function set_interact_as_actor(int $actorId): void + { + if (auth()->loggedIn()) { + session() + ->set('interact_as_actor_id', $actorId); + } + } +} + +if (! function_exists('remove_interact_as_actor')) { + /** + * Removes the actor id of which the user is acting as + */ + function remove_interact_as_actor(): void + { + session()->remove('interact_as_actor_id'); + } +} + +if (! function_exists('interact_as_actor_id')) { + /** + * Sets the podcast id of which the user is acting as + */ + function interact_as_actor_id(): ?int + { + return session()->get('interact_as_actor_id'); + } +} + +if (! function_exists('interact_as_actor')) { + /** + * Get the actor the user is currently interacting as + */ + function interact_as_actor(): ?Actor + { + if (! auth()->loggedIn()) { + return null; + } + + $session = session(); + if (! $session->has('interact_as_actor_id')) { + return null; + } + + return model(ActorModel::class, false)->getActorById($session->get('interact_as_actor_id')); + } +} + +if (! function_exists('can_user_interact')) { + function can_user_interact(): bool + { + return (bool) interact_as_actor(); + } +} + +if (! function_exists('add_podcast_group')) { + function add_podcast_group(User $user, int $podcastId, string $group): User + { + $podcastGroup = 'podcast#' . $podcastId . '-' . $group; + + return $user->addGroup($podcastGroup); + } +} + +if (! function_exists('get_instance_group')) { + function get_instance_group(User $user): ?string + { + $instanceGroups = array_filter( + $user->getGroups() ?? [], + static fn ($group): bool => ! str_starts_with((string) $group, 'podcast#'), + ); + + if ($instanceGroups === []) { + return null; + } + + $instanceGroup = array_shift($instanceGroups); + + // Verify that a user belongs to one group only! + if ($instanceGroups !== []) { + // remove any other group the user belongs to + $user->removeGroup(...$instanceGroups); + } + + return $instanceGroup; + } +} + +if (! function_exists('set_instance_group')) { + function set_instance_group(User $user, string $group): User + { + // remove old instance group + if (get_instance_group($user)) { + $user->removeGroup(get_instance_group($user)); + } + + // set new group + return $user->addGroup($group); + } +} + +if (! function_exists('get_podcast_group')) { + function get_podcast_group(User $user, int $podcastId, bool $removePrefix = true): ?string + { + $podcastGroups = array_filter( + $user->getGroups() ?? [], + static fn ($group): bool => str_starts_with((string) $group, "podcast#{$podcastId}-"), + ); + + if ($podcastGroups === []) { + return null; + } + + $podcastGroup = array_shift($podcastGroups); + + // Verify that a user belongs to one group only! + if ($podcastGroups !== []) { + // remove any other group the user belongs to + $user->removeGroup(...$podcastGroups); + } + + if ($removePrefix) { + // strip the `podcast#{id}-` prefix when returning group + return substr((string) $podcastGroup, strlen('podcast#' . $podcastId . '-')); + } + + return $podcastGroup; + } +} + +if (! function_exists('set_podcast_group')) { + function set_podcast_group(User $user, int $podcastId, string $group): User + { + // remove old instance group + $user->removeGroup("podcast#{$podcastId}-" . get_podcast_group($user, $podcastId)); + + // set new group + return add_podcast_group($user, $podcastId, $group); + } +} + +if (! function_exists('get_podcast_groups')) { + /** + * @return string[] + */ + function get_user_podcast_ids(User $user): array + { + $podcastGroups = array_filter( + $user->getGroups() ?? [], + static fn ($group): bool => str_starts_with((string) $group, 'podcast#'), + ); + + $userPodcastIds = []; + // extract all podcast ids from groups + foreach ($podcastGroups as $podcastGroup) { + // extract podcast id from group and add it to the list of ids + preg_match('~podcast#(\d+)-[a-z]+~', (string) $podcastGroup, $matches); + $userPodcastIds[] = $matches[1]; + } + + return $userPodcastIds; + } +} + +if (! function_exists('can_podcast')) { + function can_podcast(User $user, int $podcastId, string $permission): bool + { + return $user->can('podcast#' . $podcastId . '.' . $permission); + } +} + +if (! function_exists('get_user_podcasts')) { + /** + * Returns the podcasts the user is contributing to + * + * @return Podcast[] + */ + function get_user_podcasts(User $user): array + { + return new PodcastModel() + ->getUserPodcasts($user->id, get_user_podcast_ids($user)); + } +} + +if (! function_exists('get_podcasts_user_can_interact_with')) { + /** + * @return Podcast[] + */ + function get_podcasts_user_can_interact_with(User $user): array + { + $userPodcasts = new PodcastModel() + ->getUserPodcasts($user->id, get_user_podcast_ids($user)); + + $hasInteractAsPrivilege = interact_as_actor_id() === null; + + if ($userPodcasts === []) { + if ($hasInteractAsPrivilege) { + remove_interact_as_actor(); + } + + return []; + } + + $isInteractAsPrivilegeLost = true; + $podcastsUserCanInteractWith = []; + foreach ($userPodcasts as $userPodcast) { + if (can_podcast($user, $userPodcast->id, 'interact-as')) { + if (interact_as_actor_id() === $userPodcast->actor_id) { + $isInteractAsPrivilegeLost = false; + } + + $podcastsUserCanInteractWith[] = $userPodcast; + } + } + + if ($podcastsUserCanInteractWith === []) { + if (interact_as_actor_id() !== null) { + remove_interact_as_actor(); + } + + return []; + } + + // check if user has lost the interact as privilege for current podcast actor. + // --> Remove interact as if there's no podcast actor to interact as + // or set the first podcast actor the user can interact as + if ($isInteractAsPrivilegeLost) { + set_interact_as_actor($podcastsUserCanInteractWith[0]->actor_id); + } + + return $podcastsUserCanInteractWith; + } +} + +if (! function_exists('get_actor_ids_with_unread_notifications')) { + /** + * Returns the ids of the user's actors that have unread notifications + * + * @return int[] + */ + function get_actor_ids_with_unread_notifications(User $user): array + { + if (($userPodcasts = get_user_podcasts($user)) === []) { + return []; + } + + $unreadNotifications = new NotificationModel() + ->whereIn('target_actor_id', array_column($userPodcasts, 'actor_id')) + ->where('read_at') + ->findAll(); + + return array_column($unreadNotifications, 'target_actor_id'); + } +} + +if (! function_exists('get_group_title')) { + /** + * @return array<'title'|'description', string> + */ + function get_group_info(string $group, ?int $podcastId = null): array + { + if ($podcastId === null) { + return setting('AuthGroups.instanceGroups')[$group]; + } + + return setting('AuthGroups.podcastGroups')[$group]; + } +} diff --git a/modules/Auth/Language/ar/Auth.php b/modules/Auth/Language/ar/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/ar/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Admin/Language/ar/Contributor.php b/modules/Auth/Language/ar/Contributor.php similarity index 72% rename from modules/Admin/Language/ar/Contributor.php rename to modules/Auth/Language/ar/Contributor.php index 92ddf0b8..4731c799 100644 --- a/modules/Admin/Language/ar/Contributor.php +++ b/modules/Auth/Language/ar/Contributor.php @@ -28,10 +28,16 @@ return [ 'submit_add' => 'إضافة مساهم', 'submit_edit' => 'حدّث الدور', ], - 'roles' => [ - 'podcast_admin' => 'Podcast admin', + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', ], 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", 'removeOwnerError' => "لا يمكنك إزالة صاحب البودكاست!", 'removeSuccess' => 'You have successfully removed {username} from {podcastTitle}', diff --git a/modules/Admin/Language/ar/MyAccount.php b/modules/Auth/Language/ar/MyAccount.php similarity index 100% rename from modules/Admin/Language/ar/MyAccount.php rename to modules/Auth/Language/ar/MyAccount.php diff --git a/modules/Admin/Language/ar/User.php b/modules/Auth/Language/ar/User.php similarity index 75% rename from modules/Admin/Language/ar/User.php rename to modules/Auth/Language/ar/User.php index f2a33409..39f78c2e 100644 --- a/modules/Admin/Language/ar/User.php +++ b/modules/Auth/Language/ar/User.php @@ -9,8 +9,7 @@ declare(strict_types=1); */ return [ - 'edit_roles' => "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', + 'edit_role' => "Edit {username}'s role", 'ban' => 'Ban', 'unban' => 'Unban', 'delete' => 'احذف', @@ -19,7 +18,7 @@ return [ 'all_users' => 'كافة المستخدمين', 'list' => [ 'user' => 'مستخدم', - 'roles' => 'الأدوار', + 'role' => 'Role', 'banned' => 'Banned?', ], 'form' => [ @@ -27,28 +26,33 @@ return [ 'username' => 'اسم المستخدم', 'password' => 'كلمة المرور', 'new_password' => 'كلمة المرور الجديدة', + 'role' => 'Role', 'roles' => 'الأدوار', 'permissions' => 'Permissions', 'submit_create' => 'Create user', 'submit_edit' => 'حفظ', 'submit_password_change' => 'Change!', ], - 'roles' => [ - 'superadmin' => 'Super admin', + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', ], 'messages' => [ 'createSuccess' => 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "{username}'s roles have been successfully updated.", - 'forcePassResetSuccess' => - '{username} will be prompted with a password reset upon next visit.', 'banSuccess' => '{username} has been banned.', 'unbanSuccess' => '{username} has been unbanned.', 'editOwnerError' => '{username} is the instance owner, you cannot edit its roles.', 'banSuperAdminError' => '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', 'deleteSuperAdminError' => '{username} is a superadmin, one does not simply delete a superadmin…', 'deleteSuccess' => '{username} has been deleted.', diff --git a/modules/Auth/Language/br/Auth.php b/modules/Auth/Language/br/Auth.php new file mode 100644 index 00000000..9730b849 --- /dev/null +++ b/modules/Auth/Language/br/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Perc\'henn·ez an istañs', + 'description' => 'Perc\'henn·ez Castopod.', + ], + 'superadmin' => [ + 'title' => 'Dreistmerour·ez', + 'description' => 'Ur c\'hontroll klok en deus war Castopod.', + ], + 'manager' => [ + 'title' => 'Merour·ez', + 'description' => 'Merañ a ra endalc\'had Castopod.', + ], + 'podcaster' => [ + 'title' => 'Podkaster', + 'description' => 'Implijerien·ezed kustum Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Gallout a ra gwelet taolenn-stur Castopod.', + 'admin.settings' => 'Gallout a ra gwelet arventennoù Castopod.', + 'users.manage' => 'Gallout a ra ober war-dro implijerien·ezed Castopod.', + 'persons.manage' => 'Gallout a ra merañ an emellerien·ezed.', + 'pages.manage' => 'Gallout a ra merañ ar pajennoù.', + 'podcasts.view' => 'Gallout a ra gwelet an holl bodkastoù.', + 'podcasts.create' => 'Gallout a ra krouiñ podkastoù nevez.', + 'podcasts.import' => 'Gallout a ra enporzhiañ podkastoù.', + 'fediverse.manage-blocks' => 'Gallout a ra mirout aktourien·ezed pe domanioù ar Fediverse ouzh kaout darempredoù gant Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Perc\'henn·ez ar podkast', + 'description' => 'Perc\'henn·ez ar podkast.', + ], + 'admin' => [ + 'title' => 'Merour·ez', + 'description' => 'Ur c\'hontroll klok en deus war ar podkast #{id}.', + ], + 'editor' => [ + 'title' => 'Embanner', + 'description' => 'Merañ a ra endalc\'had hag embannadurioù ar podkast #{id}.', + ], + 'author' => [ + 'title' => 'Aozer·ez', + 'description' => 'Merañ a ra endalc\'had ar podkast #{id} met ne c\'hall ket embann anezho.', + ], + 'guest' => [ + 'title' => 'Kouviad·ez', + 'description' => 'Perzhiad·ez eus ar podkast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Gallout a ra gwelet taolenn-stur ha muzulioù heklev ar podkast #{id}.', + 'edit' => 'Gallout a ra kemmañ ar podkast #{id}.', + 'delete' => 'Gallout a ra lemel ar podkast #{id}.', + 'manage-import' => 'Gallout a ra sinkronekaat ar podkast enporzhiet #{id}.', + 'manage-persons' => 'Gallout a ra merañ koumanantoù ar podkast #{id}.', + 'manage-subscriptions' => 'Gallout a ra merañ koumanantoù ar podkast #{id}.', + 'manage-contributors' => 'Gallout a ra merañ perzhidi ha perzhiadezed ar podkast #{id}.', + 'manage-platforms' => 'Gallout a ra ouzhpennañ pe lemel liammoù etrezek savennoù diavaez evit ar podkast #{id}.', + 'manage-publications' => 'Gallout a ra embann ar podkast #{id}.', + 'manage-notifications' => 'Gallout a ra gwelet kemennoù ar podkast #{id} ha lakaat anezho evel lennet.', + 'interact-as' => 'Gallout a ra ober traoù gant identelezh ar podkast #{id}: ouzhpennañ ur gemennadenn d\'ar re garetañ, rannañ anezhi pe respont dezhi.', + 'episodes' => [ + 'view' => 'Gallout a ra gwelet taolennoù-stur ha muzulioù heklev rannoù ar podkast #{id}.', + 'create' => 'Gallout a ra krouiñ rannoù evit podkast #{id}.', + 'edit' => 'Gallout a ra kemmañ rannoù ar podkast #{id}.', + 'delete' => 'Gallout a ra lemel rannoù ar podkast #{id}.', + 'manage-persons' => 'Gallout a ra merañ emellerien·ezed ar podkast #{id}.', + 'manage-clips' => 'Gallout a ra merañ klipoù video pe tennadoù son ar podkast #{id}.', + 'manage-publications' => 'Gallout a ra embann pe diembann rannoù ha kemennadennoù ar podkast #{id}.', + 'manage-comments' => 'Gallout a ra krouiñ/lemel evezhiadennoù evit rannoù ar podkast #{id}.', + ], + ], + + // missing keys + 'code' => 'Ho kod 6 sifr dezhañ', + + 'set_password' => 'Skrivit ho ker-tremen', + + // Welcome email + 'welcomeSubject' => 'Pedet oc\'h bet da vont war {siteName}', + 'emailWelcomeMailBody' => 'Krouet ez eus bet ur gont deoc\'h war {domain}. Klikit war al liamm amañ-dindan evit choaz ur ger-tremen. Bev e vo al liamm betek {numberOfHours} eur goude m\'eo bet kaset ar postel-mañ.', +]; diff --git a/modules/Admin/Language/br/Contributor.php b/modules/Auth/Language/br/Contributor.php similarity index 67% rename from modules/Admin/Language/br/Contributor.php rename to modules/Auth/Language/br/Contributor.php index 637fe9f1..bffc79e0 100644 --- a/modules/Admin/Language/br/Contributor.php +++ b/modules/Auth/Language/br/Contributor.php @@ -13,7 +13,7 @@ return [ 'view' => "Perzh {username} e {podcastTitle}", 'add' => 'Ouzhpennañ ur perzhiad pe ur berzhiadez', 'add_contributor' => 'Ouzhpennañ ur perzhiad pe ur berzhiadez da {0}', - 'edit_role' => 'Hizivaat roll {0}', + 'edit_role' => 'Nevesaat roll {0}', 'edit' => 'Kemmañ', 'remove' => 'Lemel', 'list' => [ @@ -26,12 +26,18 @@ return [ 'role' => 'Roll', 'role_placeholder' => 'Dibabit e·he roll…', 'submit_add' => 'Ouzhpennañ ur perzhiad pe ur berzhiadez', - 'submit_edit' => 'Hizivaat ar roll', + 'submit_edit' => 'Nevesaat ar roll', ], - 'roles' => [ - 'podcast_admin' => 'Merour podkastoù', + 'delete_form' => [ + 'title' => 'Dilemel {contributor}', + 'disclaimer' => + 'Emaoc\'h o vont da lemel {contributor} eus ar berzhidi/perzhiadezed. Ne c\'hallo ket gwelet "{podcastTitle}" ken.', + 'understand' => 'Komprenet em eus. Fellout a ra din lemel {contributor} eus "{podcastTitle}"', + 'submit' => 'Lemel', ], 'messages' => [ + 'editSuccess' => 'Cheñchet eo bet ar roll gant berzh!', + 'editOwnerError' => "Ne c'hellit ket kemmañ perc'henn·ez ar podkast!", 'removeOwnerError' => "Ne c'hellit ket lemel perc'henn ar podkast!", 'removeSuccess' => 'Lamet ho peus {username} diouzh {podcastTitle} gant berzh', diff --git a/modules/Admin/Language/br/MyAccount.php b/modules/Auth/Language/br/MyAccount.php similarity index 100% rename from modules/Admin/Language/br/MyAccount.php rename to modules/Auth/Language/br/MyAccount.php diff --git a/modules/Admin/Language/br/User.php b/modules/Auth/Language/br/User.php similarity index 50% rename from modules/Admin/Language/br/User.php rename to modules/Auth/Language/br/User.php index 314e676a..accd5af9 100644 --- a/modules/Admin/Language/br/User.php +++ b/modules/Auth/Language/br/User.php @@ -9,48 +9,52 @@ declare(strict_types=1); */ return [ - 'edit_roles' => "Kemm rolloù {username}", - 'forcePassReset' => 'Force pass reset', - 'ban' => 'Ban', - 'unban' => 'Unban', + 'edit_role' => "Kemmañ roll {username}", + 'ban' => 'Stankañ', + 'unban' => 'Distankañ', 'delete' => 'Dilemel', 'create' => 'Krouiñ un implijer·ez', 'view' => "Titouroù diwar-benn {username}", 'all_users' => 'An holl implijerien·ezed', 'list' => [ 'user' => 'Implijer·ez', - 'roles' => 'Rolloù', - 'banned' => 'Banned?', + 'role' => 'Roll', + 'banned' => 'Stanket?', ], 'form' => [ 'email' => 'Postel', 'username' => 'Anv implijer·ez', 'password' => 'Ger-tremen', 'new_password' => 'Ger-tremen nevez', + 'role' => 'Roll', 'roles' => 'Rolloù', 'permissions' => 'Aotreoù', 'submit_create' => 'Krouiñ an implijer·ez', 'submit_edit' => 'Enrollañ', 'submit_password_change' => 'Kemm!', ], - 'roles' => [ - 'superadmin' => 'Super admin', + 'delete_form' => [ + 'title' => 'Dilemel {user}', + 'disclaimer' => + "Emaoc'h o vont da lemel {user} da vat. Ne c'hallo ket gwelet an daolenn-stur ken.", + 'understand' => 'Komprenet em eus. Fellout a ra din lemel {user} da vat', + 'submit' => 'Dilemel', ], 'messages' => [ 'createSuccess' => 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => - "{username}'s roles have been successfully updated.", - 'forcePassResetSuccess' => - '{username} will be prompted with a password reset upon next visit.', - 'banSuccess' => '{username} has been banned.', - 'unbanSuccess' => '{username} has been unbanned.', + 'roleEditSuccess' => + "Rolloù {username} zo bet nevesaet gant berzh.", + 'banSuccess' => 'Stanket eo bet {username}.', + 'unbanSuccess' => 'Distanket eo bet {username}.', 'editOwnerError' => - '{username} is the instance owner, you cannot edit its roles.', + '{username} eo perc\'henn·ez an istañs. Ne c\'hallit ket kemmañ e rolloù…', 'banSuperAdminError' => - '{username} is a superadmin, one does not simply ban a superadmin…', + 'Dreistmerour·ez eo {username}, n\'haller ket stankañ un dreistmerour·ez ken aes-se…', + 'deleteOwnerError' => + '{username} eo perc\'henn·ez an istañs. N\'haller ket lemel ar perc\'henn·ez ken aes-se…', 'deleteSuperAdminError' => - '{username} is a superadmin, one does not simply delete a superadmin…', + 'Dreistmerour·ez eo {username}, n\'haller ket lemel un dreistmerour·ez ken aes-se…', 'deleteSuccess' => 'Dilamet eo bet {username}.', ], ]; diff --git a/modules/Auth/Language/ca/Auth.php b/modules/Auth/Language/ca/Auth.php new file mode 100644 index 00000000..3478f697 --- /dev/null +++ b/modules/Auth/Language/ca/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Propietari de la instància', + 'description' => 'Propietari del Castopod.', + ], + 'superadmin' => [ + 'title' => 'Super administrador', + 'description' => 'Té control complet sobre Castopod.', + ], + 'manager' => [ + 'title' => 'Administrador', + 'description' => 'Administra el contingut de Castopod.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'Usos generals de Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Pot accedir a l\'àrea d\'administració de Castopod.', + 'admin.settings' => 'Pot accedir a la configuració de Castopod.', + 'users.manage' => 'Pot administrar els usuaris de Castopod.', + 'persons.manage' => 'Pot administrar persones.', + 'pages.manage' => 'Pot administrar pàgines.', + 'podcasts.view' => 'Pot veure els pòdcasts.', + 'podcasts.create' => 'Pot crear nous pòdcasts.', + 'podcasts.import' => 'Pot importar pòdcasts.', + 'fediverse.manage-blocks' => 'Pot evitar que actors/dominis del fedivers interactuen amb Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Propietari del pòdcast', + 'description' => 'El propietari del pòdcast.', + ], + 'admin' => [ + 'title' => 'Administrador', + 'description' => 'Té control complet del pòdcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Administra els continguts i la publicació del pòdcast #{id}.', + ], + 'author' => [ + 'title' => 'Autor', + 'description' => 'Administra el contingut del podcast #{id} però no el pot publicar.', + ], + 'guest' => [ + 'title' => 'Convidat', + 'description' => 'Col·laborador general del podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Pot veure el tauler i les estadístiques del podcast #{id}.', + 'edit' => 'Pot editar el podcast #{id}.', + 'delete' => 'Pot suprimir el podcast #{id}.', + 'manage-import' => 'Pot sincronitzar el podcast importat #{id}.', + 'manage-persons' => 'Pot gestionar les subscripcions del podcast #{id}.', + 'manage-subscriptions' => 'Pot gestionar les subscripcions del podcast #{id}.', + 'manage-contributors' => 'Pot gestionar els col·laboradors del podcast #{id}.', + 'manage-platforms' => 'Pot establir/eliminar enllaços de plataforma del podcast #{id}.', + 'manage-publications' => 'Pot publicar el podcast #{id}.', + 'manage-notifications' => 'Pot veure i marcar les notificacions com a llegides per al podcast #{id}.', + 'interact-as' => 'Pot interactuar en nom del podcast #{id} per marcar les publicacions com a preferides, compartir-les o respondre-hi.', + 'episodes' => [ + 'view' => 'Pot veure taulers i estadístiques dels episodis del podcast #{id}.', + 'create' => 'Pot crear episodis per al podcast #{id}.', + 'edit' => 'Pot editar episodis del podcast #{id}.', + 'delete' => 'Pot suprimir episodis del podcast #{id}.', + 'manage-persons' => 'Pot gestionar persones d\'episodi del podcast #{id}.', + 'manage-clips' => 'Pot gestionar clips de vídeo o fragments de so del pòdcast #{id}.', + 'manage-publications' => 'Pot publicar/anul·lar la publicació d\'episodis i publicacions del pòdcast #{id}.', + 'manage-comments' => 'Pot crear/eliminar comentaris d\'episodi del pòdcast #{id}.', + ], + ], + + // missing keys + 'code' => 'El teu codi de 6 dígits', + + 'set_password' => 'Estableix la teva contrasenya', + + // Welcome email + 'welcomeSubject' => 'Has estat convidat a {siteName}', + 'emailWelcomeMailBody' => 'S\'ha creat un compte per a tu a {domain}, fes clic a l\'enllaç d\'inici de sessió següent per configurar la teva contrasenya. L\'enllaç és vàlid durant {numberOfHours} hores després de l\'hora d\'enviament d\'aquest correu electrònic.', +]; diff --git a/modules/Admin/Language/ca/Contributor.php b/modules/Auth/Language/ca/Contributor.php similarity index 72% rename from modules/Admin/Language/ca/Contributor.php rename to modules/Auth/Language/ca/Contributor.php index 61a8b380..4105f38e 100644 --- a/modules/Admin/Language/ca/Contributor.php +++ b/modules/Auth/Language/ca/Contributor.php @@ -28,10 +28,16 @@ return [ 'submit_add' => 'Afegir un col·laborador', 'submit_edit' => 'Actualitzar el rol', ], - 'roles' => [ - 'podcast_admin' => 'Administrador del podcast', + 'delete_form' => [ + 'title' => 'Suprimeix {contributor}', + 'disclaimer' => + 'Esteu a punt d\'eliminar {contributor} dels col·laboradors. Ja no podrà accedir a "{podcastTitle}".', + 'understand' => 'Entenc, vull eliminar {contributor} de "{podcastTitle}"', + 'submit' => 'Suprimeix', ], 'messages' => [ + 'editSuccess' => 'El rol ha canviat correctament!', + 'editOwnerError' => "No pots editar el propietari del podcast!", 'removeOwnerError' => "No podeu eliminar al propietari del podcast!", 'removeSuccess' => 'S\'ha eliminat a {username} de {podcastTitle}', diff --git a/modules/Admin/Language/ca/MyAccount.php b/modules/Auth/Language/ca/MyAccount.php similarity index 100% rename from modules/Admin/Language/ca/MyAccount.php rename to modules/Auth/Language/ca/MyAccount.php diff --git a/modules/Admin/Language/ca/User.php b/modules/Auth/Language/ca/User.php similarity index 74% rename from modules/Admin/Language/ca/User.php rename to modules/Auth/Language/ca/User.php index 9e2f172a..c269cfa4 100644 --- a/modules/Admin/Language/ca/User.php +++ b/modules/Auth/Language/ca/User.php @@ -9,8 +9,7 @@ declare(strict_types=1); */ return [ - 'edit_roles' => "Editar els rols de {username}", - 'forcePassReset' => 'Força el restabliment de la contrasenya', + 'edit_role' => "Editeu el rol de {username}", 'ban' => 'Bandejar', 'unban' => 'Re-admetre', 'delete' => 'Eliminar', @@ -19,7 +18,7 @@ return [ 'all_users' => 'Tots els usuaris', 'list' => [ 'user' => 'Usuari', - 'roles' => 'Rols', + 'role' => 'Rol', 'banned' => 'Bandejat?', ], 'form' => [ @@ -27,28 +26,33 @@ return [ 'username' => 'Nom de l\'usuari', 'password' => 'Contrasenya', 'new_password' => 'Nova contrasenya', + 'role' => 'Rol', 'roles' => 'Rols', 'permissions' => 'Permisos', 'submit_create' => 'Crea un usuari', 'submit_edit' => 'Desar', 'submit_password_change' => 'Canviat!', ], - 'roles' => [ - 'superadmin' => 'Super administrador/a', + 'delete_form' => [ + 'title' => 'Suprimeix {user}', + 'disclaimer' => + "Esteu a punt de suprimir {user} permanentment. Ja no podrà accedir a l'àrea d'administració.", + 'understand' => 'Entenc, vull suprimir {user} permanentment', + 'submit' => 'Suprimeix', ], 'messages' => [ 'createSuccess' => 'S\'ha creat l\'usuari! Es demanarà a {username} un restabliment de la contrasenya durant la primera autenticació.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "S'han actualitzat correctament els rols de {username}.", - 'forcePassResetSuccess' => - 'Es demanarà a {username} un restabliment de contrasenya durant la següent visita.', 'banSuccess' => '{username} ha estat bandejat.', 'unbanSuccess' => '{username} ha estat desbandejat.', 'editOwnerError' => '{username} is the instance owner, you cannot edit its roles.', 'banSuperAdminError' => '{username} és un superadministrador, hom simplement no bandeja a un superadministrador...', + 'deleteOwnerError' => + '{username} és el propietari de la instància, un no pot suprimir senzillament el propietari…', 'deleteSuperAdminError' => '{username} és un superadministrador, hom simplement no elimina a un superadministrador...', 'deleteSuccess' => '{username} ha estat eliminat.', diff --git a/modules/Auth/Language/da/Auth.php b/modules/Auth/Language/da/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/da/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Auth/Language/da/Contributor.php b/modules/Auth/Language/da/Contributor.php new file mode 100644 index 00000000..f2685498 --- /dev/null +++ b/modules/Auth/Language/da/Contributor.php @@ -0,0 +1,47 @@ + 'Podcast bidragsydere', + 'view' => "{username}s bidrag til {podcastTitle}", + 'add' => 'Tilføj bidragyder', + 'add_contributor' => 'Tilføj bidragyder til {0}', + 'edit_role' => 'Opdatér rolle for {0}', + 'edit' => 'Redigér', + 'remove' => 'Fjern', + 'list' => [ + 'username' => 'Brugernavn', + 'role' => 'Rolle', + ], + 'form' => [ + 'user' => 'Bruger', + 'user_placeholder' => 'Vælg en bruger…', + 'role' => 'Rolle', + 'role_placeholder' => 'Vælg dens rolle…', + 'submit_add' => 'Tilføj bidragyder', + 'submit_edit' => 'Opdatér rolle', + ], + 'delete_form' => [ + 'title' => 'Fjern {contributor}', + 'disclaimer' => + 'Du er ved at fjerne {contributor} fra bidragydere. De vil ikke længere kunne få adgang til "{podcastTitle}".', + 'understand' => 'Jeg forstår, jeg vil fjerne {contributor} fra "{podcastTitle}"', + 'submit' => 'Fjern', + ], + 'messages' => [ + 'editSuccess' => 'Rolle ændret!', + 'editOwnerError' => "Du kan ikke redigere podcast-ejeren!", + 'removeOwnerError' => "Du kan ikke fjerne podcast-ejeren!", + 'removeSuccess' => + 'Du har fjernet {username} fra {podcastTitle}', + 'alreadyAddedError' => + "Den bidragsyder, du forsøger at tilføje, er allerede blevet tilføjet!", + ], +]; diff --git a/modules/Admin/Language/en/MyAccount.php b/modules/Auth/Language/da/MyAccount.php similarity index 100% rename from modules/Admin/Language/en/MyAccount.php rename to modules/Auth/Language/da/MyAccount.php diff --git a/modules/Auth/Language/da/User.php b/modules/Auth/Language/da/User.php new file mode 100644 index 00000000..32ec560c --- /dev/null +++ b/modules/Auth/Language/da/User.php @@ -0,0 +1,60 @@ + "Edit {username}'s role", + 'ban' => 'Ban', + 'unban' => 'Unban', + 'delete' => 'Delete', + 'create' => 'New user', + 'view' => "{username}'s info", + 'all_users' => 'All users', + 'list' => [ + 'user' => 'User', + 'role' => 'Role', + 'banned' => 'Banned?', + ], + 'form' => [ + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + 'new_password' => 'New Password', + 'role' => 'Role', + 'roles' => 'Roles', + 'permissions' => 'Permissions', + 'submit_create' => 'Create user', + 'submit_edit' => 'Save', + 'submit_password_change' => 'Change!', + ], + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', + ], + 'messages' => [ + 'createSuccess' => + 'User created successfully! A welcome email was sent to {username} with a login link, they will be prompted with a password reset upon first authentication.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + 'banSuccess' => '{username} has been banned.', + 'unbanSuccess' => '{username} has been unbanned.', + 'editOwnerError' => + '{username} is the instance owner, one does not simply touch the owner…', + 'banSuperAdminError' => + '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', + 'deleteSuperAdminError' => + '{username} is a superadmin, one does not simply delete a superadmin…', + 'deleteSuccess' => '{username} has been deleted.', + ], +]; diff --git a/modules/Auth/Language/de/Auth.php b/modules/Auth/Language/de/Auth.php new file mode 100644 index 00000000..e35619ca --- /dev/null +++ b/modules/Auth/Language/de/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instanzbesitzer', + 'description' => 'Der Castopod-Besitzer.', + ], + 'superadmin' => [ + 'title' => 'Super-Administrator', + 'description' => 'Hat die vollständige Kontrolle über Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Verwaltet Castopods Inhalte.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'Allgemeine Benutzer von Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Kann auf den Admin-Bereich von Castopod zugreifen.', + 'admin.settings' => 'Kann auf die Einstellungen von Castopod zugreifen.', + 'users.manage' => 'Kann Castopod-Benutzer verwalten.', + 'persons.manage' => 'Kann Mitwirkende verwalten.', + 'pages.manage' => 'Kann Seiten verwalten.', + 'podcasts.view' => 'Kann alle Podcasts einsehen.', + 'podcasts.create' => 'Kann neue Podcasts erstellen.', + 'podcasts.import' => 'Kann Podcasts importieren.', + 'fediverse.manage-blocks' => 'Kann föderierte Nutzer/Domains davon abhalten, mit Castopod zu interagieren.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast-Besitzer', + 'description' => 'Der Podcast-Besitzer.', + ], + 'admin' => [ + 'title' => 'Administrator', + 'description' => 'Hat die vollständige Kontrolle über Podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Verwaltet Inhalte und Veröffentlichungen von Podcast #{id}.', + ], + 'author' => [ + 'title' => 'Autor', + 'description' => 'Verwaltet Inhalte von Podcast #{id}, kann diese aber nicht veröffentlichen.', + ], + 'guest' => [ + 'title' => 'Gast', + 'description' => 'Allgemeiner Mitwirkender des Podcasts #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Kann das Dashboard und Analysen des Podcasts #{id} einsehen.', + 'edit' => 'Kann Podcast #{id} bearbeiten.', + 'delete' => 'Kann Podcast #{id} löschen.', + 'manage-import' => 'Kann den importierten Podcast #{id} synchronisieren.', + 'manage-persons' => 'Kann Abonnements des Podcasts #{id} verwalten.', + 'manage-subscriptions' => 'Kann Abonnements des Podcasts #{id} verwalten.', + 'manage-contributors' => 'Kann Mitwirkende des Podcasts #{id} verwalten.', + 'manage-platforms' => 'Kann Plattform-Links des Podcasts #{id} verwalten.', + 'manage-publications' => 'Kann Podcast #{id} veröffentlichen.', + 'manage-notifications' => 'Kann Benachrichtigungen des Podcasts #{id} einsehen und als gelesen markieren.', + 'interact-as' => 'Kann als Podcast #{id} interagieren, um Beiträge zu favorisieren, zu teilen oder diese zu beantworten.', + 'episodes' => [ + 'view' => 'Kann Dashboards und Analysen von Episoden des Podcasts #{id} einsehen.', + 'create' => 'Kann Folgen für Podcast #{id} erstellen.', + 'edit' => 'Kann Folgen von Podcast #{id} bearbeiten.', + 'delete' => 'Kann Folgen von Podcast #{id} löschen.', + 'manage-persons' => 'Kann Personen von Episoden des Podcasts #{id} verwalten.', + 'manage-clips' => 'Kann Videoclips und Soundbites des Podcasts #{id} verwalten.', + 'manage-publications' => 'Kann Episoden und Posts von Podcast #{id} veröffentlichen/zurückziehen.', + 'manage-comments' => 'Kann Kommentare von Folgen des Podcasts #{id} erstellen und löschen.', + ], + ], + + // missing keys + 'code' => 'Ihr 6-stelliger Code', + + 'set_password' => 'Legen Sie Ihr Passwort fest', + + // Welcome email + 'welcomeSubject' => 'Sie wurden zu {siteName} eingeladen', + 'emailWelcomeMailBody' => 'Ein Account auf {domain} wurde für Sie angelegt, klicken Sie auf den unten stehenden Login-Link, um Ihr Passwort festzulegen. Der Link ist mit Versand der Mail für {numberOfHours} gültig.', +]; diff --git a/modules/Admin/Language/de/Contributor.php b/modules/Auth/Language/de/Contributor.php similarity index 60% rename from modules/Admin/Language/de/Contributor.php rename to modules/Auth/Language/de/Contributor.php index c21e05b8..27ff60dc 100644 --- a/modules/Admin/Language/de/Contributor.php +++ b/modules/Auth/Language/de/Contributor.php @@ -10,9 +10,9 @@ declare(strict_types=1); return [ 'podcast_contributors' => 'Podcast-Administratoren', - 'view' => "{username}'s Administration von {podcastTitle}", - 'add' => 'Adminstrator zufügen', - 'add_contributor' => 'Administrator zufügen für {0}', + 'view' => "{username}'s Mitwirkung an {podcastTitle}", + 'add' => 'Mitwirkenden zufügen', + 'add_contributor' => 'Mitwirkenden zufügen für {0}', 'edit_role' => 'Rolle aktualisieren für {0}', 'edit' => 'Bearbeiten', 'remove' => 'Entfernen', @@ -28,10 +28,16 @@ return [ 'submit_add' => 'Administrator zufügen', 'submit_edit' => 'Rolle aktualisieren', ], - 'roles' => [ - 'podcast_admin' => 'Podcast Administrator', + 'delete_form' => [ + 'title' => '{contributor} entfernen', + 'disclaimer' => + 'Sie sind dabei, {contributor} von den Mitwirkenden zu entfernen. Es wird kein Zugriff mehr auf "{podcastTitle}" möglich sein.', + 'understand' => 'Ich verstehe, ich möchte {contributor} von "{podcastTitle}" entfernen', + 'submit' => 'Entfernen', ], 'messages' => [ + 'editSuccess' => 'Rolle erfolgreich geändert!', + 'editOwnerError' => "Sie können den Podcast-Besitzer nicht verändern!", 'removeOwnerError' => "Der Podcast Inhaber kann nicht entfernt werden!", 'removeSuccess' => '{username} wurde von {podcastTitle} entfernt', diff --git a/modules/Admin/Language/de/MyAccount.php b/modules/Auth/Language/de/MyAccount.php similarity index 100% rename from modules/Admin/Language/de/MyAccount.php rename to modules/Auth/Language/de/MyAccount.php diff --git a/modules/Admin/Language/de/User.php b/modules/Auth/Language/de/User.php similarity index 63% rename from modules/Admin/Language/de/User.php rename to modules/Auth/Language/de/User.php index a4d261be..68649b0a 100644 --- a/modules/Admin/Language/de/User.php +++ b/modules/Auth/Language/de/User.php @@ -9,46 +9,50 @@ declare(strict_types=1); */ return [ - 'edit_roles' => "Bearbeite {username}'s Rollen", - 'forcePassReset' => 'Erzwinge Pass-Zurücksetzung', - 'ban' => 'Bannen', - 'unban' => 'Entbannen', + 'edit_role' => "Die Rolle(n) von {username} bearbeiten", + 'ban' => 'Sperren', + 'unban' => 'Entsperren', 'delete' => 'Löschen', 'create' => 'Neuer Benutzer', - 'view' => "{username}'s Info", + 'view' => "{username}-Infos", 'all_users' => 'Alle Benutzer', 'list' => [ 'user' => 'Benutzer', - 'roles' => 'Rollen', - 'banned' => 'Gebannt?', + 'role' => 'Rolle', + 'banned' => 'Gesperrt?', ], 'form' => [ 'email' => 'E-mail', 'username' => 'Benutzername', 'password' => 'Passwort', 'new_password' => 'Neues Passwort', + 'role' => 'Rolle', 'roles' => 'Rollen', 'permissions' => 'Berechtigungen', 'submit_create' => 'Benutzer erstellen', 'submit_edit' => 'Speichern', 'submit_password_change' => 'Verändern!', ], - 'roles' => [ - 'superadmin' => 'Super-Admin', + 'delete_form' => [ + 'title' => '{user} löschen', + 'disclaimer' => + "Sie sind dabei {user} dauerhaft zu löschen. Es wird für den Benutzer nicht mehr möglich sein, den Admin-Bereich zu nutzen.", + 'understand' => 'Ich verstehe, Ich will {user} dauerhaft löschen', + 'submit' => 'Löschen', ], 'messages' => [ 'createSuccess' => 'Benutzer wurde erfolgreich erstellt! {username} wird bei der ersten Authentifizierung zu einer Passwortzurücksetzung aufgefordert.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "{username}'s Rollen wurden erfolgreich aktualisiert.", - 'forcePassResetSuccess' => - '{username} wird beim nächsten Besuch zu einem Zurücksetzen des Passworts aufgefordert.', 'banSuccess' => '{username} wurde gebannt.', 'unbanSuccess' => '{username} wurde entbannt.', 'editOwnerError' => - '{username} is the instance owner, you cannot edit its roles.', + '{username} ist Eigentümer der Instanz, Eigentümer können nicht gelöscht werden…', 'banSuperAdminError' => '{username} ist ein Superadmin, man bannt nicht einfach einen Superadmin…', + 'deleteOwnerError' => + '{username} ist Eigentümer der Instanz, Eigentümer können nicht gelöscht werden…', 'deleteSuperAdminError' => '{username} ist ein Superadmin, man löscht nicht einfach einen Superadmin…', 'deleteSuccess' => '{username} wurde gelöscht.', diff --git a/modules/Auth/Language/el/Auth.php b/modules/Auth/Language/el/Auth.php new file mode 100644 index 00000000..1ba3c9ae --- /dev/null +++ b/modules/Auth/Language/el/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Ιδιοκτήτης Διακομιστή', + 'description' => 'Ο ιδιοκτήτης του Castopod.', + ], + 'superadmin' => [ + 'title' => 'Υπερδιαχειριστής', + 'description' => 'Έχει πλήρη έλεγχο του Castopod.', + ], + 'manager' => [ + 'title' => 'Διαχειριστής', + 'description' => 'Διαχείριση περιεχομένου του Castopod.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'Γενικοί χρήστες του Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Μπορεί να έχει πρόσβαση στην περιοχή διαχείρισης Castopod.', + 'admin.settings' => 'Μπορεί να έχει πρόσβαση στις ρυθμίσεις Castopod.', + 'users.manage' => 'Μπορεί να διαχειριστεί τους χρήστες Castopod.', + 'persons.manage' => 'Μπορεί να διαχειριστεί τα άτομα.', + 'pages.manage' => 'Μπορεί να διαχειριστεί τις σελίδες.', + 'podcasts.view' => 'Μπορεί να δει όλα τα podcasts.', + 'podcasts.create' => 'Μπορεί να δημιουργήσει νέα podcasts.', + 'podcasts.import' => 'Μπορεί να εισάγει podcasts.', + 'fediverse.manage-blocks' => 'Μπορεί να εμποδίσει τους ψευτογενείς ηθοποιούς/τομείς να αλληλεπιδρούν με το Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Ιδιοκτήτης Podcast', + 'description' => 'Ο ιδιοκτήτης του podcast.', + ], + 'admin' => [ + 'title' => 'Διαχειριστής', + 'description' => 'Έχει πλήρη έλεγχο του podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Εκδότης', + 'description' => 'Διαχειρίζεται περιεχόμενο και δημοσιεύσεις του podcast #{id}.', + ], + 'author' => [ + 'title' => 'Συντάκτης', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Επισκέπτης', + 'description' => 'Γενικός συντελεστής του podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Admin/Language/el/Contributor.php b/modules/Auth/Language/el/Contributor.php similarity index 77% rename from modules/Admin/Language/el/Contributor.php rename to modules/Auth/Language/el/Contributor.php index 132d9abb..1acec63a 100644 --- a/modules/Admin/Language/el/Contributor.php +++ b/modules/Auth/Language/el/Contributor.php @@ -28,10 +28,16 @@ return [ 'submit_add' => 'Προσθήκη συντελεστή', 'submit_edit' => 'Ενημέρωση ρόλου', ], - 'roles' => [ - 'podcast_admin' => 'Διαχειριστής Podcast', + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', ], 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", 'removeOwnerError' => "Δεν μπορείτε να καταργήσετε τον ιδιοκτήτη podcast!", 'removeSuccess' => 'Έχετε αφαιρέσει με επιτυχία τον χρήστη {username} από το {podcastTitle}', diff --git a/modules/Admin/Language/el/MyAccount.php b/modules/Auth/Language/el/MyAccount.php similarity index 100% rename from modules/Admin/Language/el/MyAccount.php rename to modules/Auth/Language/el/MyAccount.php diff --git a/modules/Auth/Language/el/User.php b/modules/Auth/Language/el/User.php new file mode 100644 index 00000000..05bc0e1d --- /dev/null +++ b/modules/Auth/Language/el/User.php @@ -0,0 +1,60 @@ + "Επεξεργασία ρόλων {username}", + 'ban' => 'Αποκλεισμός', + 'unban' => 'Κατάργηση αποκλεισμού', + 'delete' => 'Διαγραφή', + 'create' => 'Νέος χρήστης', + 'view' => "πληροφορίες του {username}", + 'all_users' => 'Όλοι οι χρήστες', + 'list' => [ + 'user' => 'Χρήστης', + 'role' => 'Ρόλος', + 'banned' => 'Αποκλεισμένος;', + ], + 'form' => [ + 'email' => 'Ηλεκτρονικό ταχυδρομείο', + 'username' => 'Όνομα Χρήστη', + 'password' => 'Κωδικόs πρόσβασης', + 'new_password' => 'Νέος Κωδικός Πρόσβασης', + 'role' => 'Ρόλος', + 'roles' => 'Ρόλοι', + 'permissions' => 'Δικαιώματα', + 'submit_create' => 'Δημιουργία χρήστη', + 'submit_edit' => 'Αποθήκευση', + 'submit_password_change' => 'Αλλαγή!', + ], + 'delete_form' => [ + 'title' => 'Διαγραφή {user}', + 'disclaimer' => + "Πρόκειται να διαγράψετε το {user} οριστικά. Δεν θα μπορούν πλέον να έχουν πρόσβαση στην περιοχή διαχείρισης.", + 'understand' => 'Καταλαβαίνω, θέλω να διαγράψω {user} μόνιμα', + 'submit' => 'Διαγραφή', + ], + 'messages' => [ + 'createSuccess' => + 'User created successfully! {username} will be prompted with a password reset upon first authentication.', + 'roleEditSuccess' => + "οι ρόλοι του {username} έχουν ενημερωθεί με επιτυχία.", + 'banSuccess' => 'Ο/Η {username} έχει αποκλειστεί.', + 'unbanSuccess' => '{username} has been unbanned.', + 'editOwnerError' => + '{username} is the instance owner, you cannot edit its roles.', + 'banSuperAdminError' => + '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', + 'deleteSuperAdminError' => + '{username} is a superadmin, one does not simply delete a superadmin…', + 'deleteSuccess' => '{username} has been deleted.', + ], +]; diff --git a/modules/Auth/Language/en/Auth.php b/modules/Auth/Language/en/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/en/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Admin/Language/en/Contributor.php b/modules/Auth/Language/en/Contributor.php similarity index 70% rename from modules/Admin/Language/en/Contributor.php rename to modules/Auth/Language/en/Contributor.php index d0f3b93d..c70badc0 100644 --- a/modules/Admin/Language/en/Contributor.php +++ b/modules/Auth/Language/en/Contributor.php @@ -28,10 +28,16 @@ return [ 'submit_add' => 'Add contributor', 'submit_edit' => 'Update role', ], - 'roles' => [ - 'podcast_admin' => 'Podcast admin', + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', ], 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", 'removeOwnerError' => "You can't remove the podcast owner!", 'removeSuccess' => 'You have successfully removed {username} from {podcastTitle}', diff --git a/modules/Admin/Language/fa/MyAccount.php b/modules/Auth/Language/en/MyAccount.php similarity index 100% rename from modules/Admin/Language/fa/MyAccount.php rename to modules/Auth/Language/en/MyAccount.php diff --git a/modules/Auth/Language/en/User.php b/modules/Auth/Language/en/User.php new file mode 100644 index 00000000..32ec560c --- /dev/null +++ b/modules/Auth/Language/en/User.php @@ -0,0 +1,60 @@ + "Edit {username}'s role", + 'ban' => 'Ban', + 'unban' => 'Unban', + 'delete' => 'Delete', + 'create' => 'New user', + 'view' => "{username}'s info", + 'all_users' => 'All users', + 'list' => [ + 'user' => 'User', + 'role' => 'Role', + 'banned' => 'Banned?', + ], + 'form' => [ + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + 'new_password' => 'New Password', + 'role' => 'Role', + 'roles' => 'Roles', + 'permissions' => 'Permissions', + 'submit_create' => 'Create user', + 'submit_edit' => 'Save', + 'submit_password_change' => 'Change!', + ], + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', + ], + 'messages' => [ + 'createSuccess' => + 'User created successfully! A welcome email was sent to {username} with a login link, they will be prompted with a password reset upon first authentication.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + 'banSuccess' => '{username} has been banned.', + 'unbanSuccess' => '{username} has been unbanned.', + 'editOwnerError' => + '{username} is the instance owner, one does not simply touch the owner…', + 'banSuperAdminError' => + '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', + 'deleteSuperAdminError' => + '{username} is a superadmin, one does not simply delete a superadmin…', + 'deleteSuccess' => '{username} has been deleted.', + ], +]; diff --git a/modules/Auth/Language/es/Auth.php b/modules/Auth/Language/es/Auth.php new file mode 100644 index 00000000..9f82a3cc --- /dev/null +++ b/modules/Auth/Language/es/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Propietario de Instancia', + 'description' => 'Propietario de Castopod.', + ], + 'superadmin' => [ + 'title' => 'Super administrador', + 'description' => 'Tiene control completo sobre Castopod.', + ], + 'manager' => [ + 'title' => 'Administrador', + 'description' => 'Administrar contenido de Castopod.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'Usuarios generales de Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Puedes acceder al área de administración de Castopod.', + 'admin.settings' => 'Puede acceder a la configuración de Castopod.', + 'users.manage' => 'Puede administrar usuarios de Castopod.', + 'persons.manage' => 'Puede administrar personas.', + 'pages.manage' => 'Puede administrar páginas.', + 'podcasts.view' => 'Puede ver todos los podcasts.', + 'podcasts.create' => 'Puede crear nuevos podcasts.', + 'podcasts.import' => 'Puede importar podcasts.', + 'fediverse.manage-blocks' => 'Puedes bloquear la interacción de actores/dominios del fediverso con Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Propietario de Podcast', + 'description' => 'El propietario del podcast.', + ], + 'admin' => [ + 'title' => 'Administrador', + 'description' => 'Tiene el control completo del podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Gestiona el contenido y las publicaciones del podcast #{id}.', + ], + 'author' => [ + 'title' => 'Autor', + 'description' => 'Gestiona el contenido del podcast #{id} pero no puede publicarlo.', + ], + 'guest' => [ + 'title' => 'Invitado', + 'description' => 'Colaborador general del podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Puede ver el panel de control y analíticas del episodio #{id}.', + 'edit' => 'Puede editar el podcast #{id}.', + 'delete' => 'Puede borrar el podcast #{id}.', + 'manage-import' => 'Puede sincronizar el podcast importado #{id}.', + 'manage-persons' => 'Puede administrar las suscripciones del podcast #{id}.', + 'manage-subscriptions' => 'Puede administrar las suscripciones del podcast #{id}.', + 'manage-contributors' => 'Puede administrar colaboradores del podcast #{id}.', + 'manage-platforms' => 'Puede establecer/eliminar enlaces a la plataforma del podcast #{id}.', + 'manage-publications' => 'Puede publicar el podcast #{id}.', + 'manage-notifications' => 'Puede ver y marcar las notificaciones como leídas para podcast #{id}.', + 'interact-as' => 'Puede interactuar como el podcast #{id} para marcar como favarito, compartir o responder a las publicaciones.', + 'episodes' => [ + 'view' => 'Puede ver el panel de control y analíticas del episodio #{id}.', + 'create' => 'Puede crear episodios para el podcast #{id}.', + 'edit' => 'Puede editar episodios del podcast #{id}.', + 'delete' => 'Puede borrar episodios del podcast #{id}.', + 'manage-persons' => 'Puede administrar las personas de los episodios del podcast #{id}.', + 'manage-clips' => 'Puedes administrar video clips o sonidos del podcast #{id}.', + 'manage-publications' => 'Puede publicar/despublicar episodios y publicaciones del podcast #{id}.', + 'manage-comments' => 'Puede crear/eliminar los comentarios de episodio del podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Introduce un código de 6 dígitos', + + 'set_password' => 'Establece tu contraseña', + + // Welcome email + 'welcomeSubject' => 'Has sido invitado a {siteName}', + 'emailWelcomeMailBody' => 'Una cuenta fue creada para usted en {domain}, haga clic en el enlace de inicio de sesión de abajo para establecer su contraseña. El enlace es válido durante {numberOfHours} horas después de enviar este correo electrónico.', +]; diff --git a/modules/Admin/Language/es/Contributor.php b/modules/Auth/Language/es/Contributor.php similarity index 72% rename from modules/Admin/Language/es/Contributor.php rename to modules/Auth/Language/es/Contributor.php index 7cd774b2..d5b3a256 100644 --- a/modules/Admin/Language/es/Contributor.php +++ b/modules/Auth/Language/es/Contributor.php @@ -28,10 +28,16 @@ return [ 'submit_add' => 'Añadir colaborador', 'submit_edit' => 'Actualizar Cargo', ], - 'roles' => [ - 'podcast_admin' => 'Administrador del Podcast', + 'delete_form' => [ + 'title' => 'Eliminar {contributor}', + 'disclaimer' => + 'Estás a punto de eliminar a {contributor} de los colaboradores. Ya no podrán acceder a "{podcastTitle}".', + 'understand' => 'Entiendo, quiero eliminar a {contributor} de "{podcastTitle}"', + 'submit' => 'Eliminar', ], 'messages' => [ + 'editSuccess' => '¡Rol cambiado con éxito!', + 'editOwnerError' => "¡No puedes editar el dueño del podcast!", 'removeOwnerError' => "¡No puedes eliminar al dueño del podcast!", 'removeSuccess' => 'Has eliminado con éxito a {username} de {podcastTitle}', diff --git a/modules/Admin/Language/es/MyAccount.php b/modules/Auth/Language/es/MyAccount.php similarity index 100% rename from modules/Admin/Language/es/MyAccount.php rename to modules/Auth/Language/es/MyAccount.php diff --git a/modules/Admin/Language/es/User.php b/modules/Auth/Language/es/User.php similarity index 74% rename from modules/Admin/Language/es/User.php rename to modules/Auth/Language/es/User.php index 1b37eec2..ce11ceb6 100644 --- a/modules/Admin/Language/es/User.php +++ b/modules/Auth/Language/es/User.php @@ -9,8 +9,7 @@ declare(strict_types=1); */ return [ - 'edit_roles' => "Editar rol de {username}", - 'forcePassReset' => 'Forzar el reseteo de la contraseña', + 'edit_role' => "Editar rol de {username}", 'ban' => 'Banear', 'unban' => 'Desbanear', 'delete' => 'Borrar', @@ -19,7 +18,7 @@ return [ 'all_users' => 'Todos los usuarios', 'list' => [ 'user' => 'Usuario', - 'roles' => 'Roles', + 'role' => 'Rol', 'banned' => '¿Baneado?', ], 'form' => [ @@ -27,28 +26,33 @@ return [ 'username' => 'Nombre de usuario', 'password' => 'Contraseña', 'new_password' => 'Nueva Contraseña', + 'role' => 'Rol', 'roles' => 'Roles', 'permissions' => 'Permisos', 'submit_create' => 'Crear usuario', 'submit_edit' => 'Guardar', 'submit_password_change' => '¡Cambiar!', ], - 'roles' => [ - 'superadmin' => 'Super administrador', + 'delete_form' => [ + 'title' => 'Eliminar {user}', + 'disclaimer' => + "Estás a punto de eliminar {user} permanentemente. Ya no podrán acceder al área de administración.", + 'understand' => 'Entiendo, quiero eliminar {user} permanentemente', + 'submit' => 'Eliminar', ], 'messages' => [ 'createSuccess' => '¡Usuario creado con éxito! Se le pedirá a {username} que restablezca la contraseña en la primera autenticación.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "Los roles de {username} se han actualizado correctamente.", - 'forcePassResetSuccess' => - 'Se pedirá a {username} que restablezca su contraseña en la próxima visita.', 'banSuccess' => '{username} ha sido baneado.', 'unbanSuccess' => '{username} ha sido desbaneado.', 'editOwnerError' => '{username} is the instance owner, you cannot edit its roles.', 'banSuperAdminError' => '{username} es un superadmin, no puedes banear a un superadministrador…', + 'deleteOwnerError' => + '{username} es el propietario de la instancia, uno no simplemente elimina al propietario…', 'deleteSuperAdminError' => '{username} es un superadmin, no puedes borrar a un superadministrador…', 'deleteSuccess' => '{username} ha sido eliminado.', diff --git a/modules/Auth/Language/eu/Auth.php b/modules/Auth/Language/eu/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/eu/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Admin/Language/fa/Contributor.php b/modules/Auth/Language/eu/Contributor.php similarity index 70% rename from modules/Admin/Language/fa/Contributor.php rename to modules/Auth/Language/eu/Contributor.php index d0f3b93d..c70badc0 100644 --- a/modules/Admin/Language/fa/Contributor.php +++ b/modules/Auth/Language/eu/Contributor.php @@ -28,10 +28,16 @@ return [ 'submit_add' => 'Add contributor', 'submit_edit' => 'Update role', ], - 'roles' => [ - 'podcast_admin' => 'Podcast admin', + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', ], 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", 'removeOwnerError' => "You can't remove the podcast owner!", 'removeSuccess' => 'You have successfully removed {username} from {podcastTitle}', diff --git a/modules/Admin/Language/gd/MyAccount.php b/modules/Auth/Language/eu/MyAccount.php similarity index 100% rename from modules/Admin/Language/gd/MyAccount.php rename to modules/Auth/Language/eu/MyAccount.php diff --git a/modules/Auth/Language/eu/User.php b/modules/Auth/Language/eu/User.php new file mode 100644 index 00000000..32ec560c --- /dev/null +++ b/modules/Auth/Language/eu/User.php @@ -0,0 +1,60 @@ + "Edit {username}'s role", + 'ban' => 'Ban', + 'unban' => 'Unban', + 'delete' => 'Delete', + 'create' => 'New user', + 'view' => "{username}'s info", + 'all_users' => 'All users', + 'list' => [ + 'user' => 'User', + 'role' => 'Role', + 'banned' => 'Banned?', + ], + 'form' => [ + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + 'new_password' => 'New Password', + 'role' => 'Role', + 'roles' => 'Roles', + 'permissions' => 'Permissions', + 'submit_create' => 'Create user', + 'submit_edit' => 'Save', + 'submit_password_change' => 'Change!', + ], + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', + ], + 'messages' => [ + 'createSuccess' => + 'User created successfully! A welcome email was sent to {username} with a login link, they will be prompted with a password reset upon first authentication.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + 'banSuccess' => '{username} has been banned.', + 'unbanSuccess' => '{username} has been unbanned.', + 'editOwnerError' => + '{username} is the instance owner, one does not simply touch the owner…', + 'banSuperAdminError' => + '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', + 'deleteSuperAdminError' => + '{username} is a superadmin, one does not simply delete a superadmin…', + 'deleteSuccess' => '{username} has been deleted.', + ], +]; diff --git a/modules/Auth/Language/fa/Auth.php b/modules/Auth/Language/fa/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/fa/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Admin/Language/id/Contributor.php b/modules/Auth/Language/fa/Contributor.php similarity index 70% rename from modules/Admin/Language/id/Contributor.php rename to modules/Auth/Language/fa/Contributor.php index d0f3b93d..c70badc0 100644 --- a/modules/Admin/Language/id/Contributor.php +++ b/modules/Auth/Language/fa/Contributor.php @@ -28,10 +28,16 @@ return [ 'submit_add' => 'Add contributor', 'submit_edit' => 'Update role', ], - 'roles' => [ - 'podcast_admin' => 'Podcast admin', + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', ], 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", 'removeOwnerError' => "You can't remove the podcast owner!", 'removeSuccess' => 'You have successfully removed {username} from {podcastTitle}', diff --git a/modules/Admin/Language/id/MyAccount.php b/modules/Auth/Language/fa/MyAccount.php similarity index 100% rename from modules/Admin/Language/id/MyAccount.php rename to modules/Auth/Language/fa/MyAccount.php diff --git a/modules/Auth/Language/fa/User.php b/modules/Auth/Language/fa/User.php new file mode 100644 index 00000000..b9804342 --- /dev/null +++ b/modules/Auth/Language/fa/User.php @@ -0,0 +1,60 @@ + "ویراش نقش {username}", + 'ban' => 'Ban', + 'unban' => 'Unban', + 'delete' => 'حذف', + 'create' => 'کاربر جدید', + 'view' => "اطّلاعات {username}", + 'all_users' => 'تمامی کاربران', + 'list' => [ + 'user' => 'کاربر', + 'role' => 'نقش', + 'banned' => 'Banned?', + ], + 'form' => [ + 'email' => 'رایانامه', + 'username' => 'نام‌کاربری', + 'password' => 'گذرواژه', + 'new_password' => 'گذرواژه‌ٔ جدید', + 'role' => 'نقش', + 'roles' => 'نقش‌ها', + 'permissions' => 'اجازه‌ها', + 'submit_create' => 'ایجاد کاربر', + 'submit_edit' => 'ذخیره', + 'submit_password_change' => 'تغییر!', + ], + 'delete_form' => [ + 'title' => 'حذف {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'حذف', + ], + 'messages' => [ + 'createSuccess' => + 'کاربر با موفّقیت ساخته شد! رایانامهٔ خوش‌آمدی به همراه پیوند ورود برای {username} فرستاده شد. در نخستین ورودش اعلانی برای بازنشانی گذرواژه دریافت خواهد کرد.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + 'banSuccess' => '{username} has been banned.', + 'unbanSuccess' => '{username} has been unbanned.', + 'editOwnerError' => + '{username} مالک نمونه است. کسی به سادگی مالک را تغییر نمی‌دهد…', + 'banSuperAdminError' => + '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', + 'deleteSuperAdminError' => + '{username} is a superadmin, one does not simply delete a superadmin…', + 'deleteSuccess' => '{username} has been deleted.', + ], +]; diff --git a/modules/Auth/Language/fr-ca/Auth.php b/modules/Auth/Language/fr-ca/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/fr-ca/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Admin/Language/gd/Contributor.php b/modules/Auth/Language/fr-ca/Contributor.php similarity index 70% rename from modules/Admin/Language/gd/Contributor.php rename to modules/Auth/Language/fr-ca/Contributor.php index d0f3b93d..c70badc0 100644 --- a/modules/Admin/Language/gd/Contributor.php +++ b/modules/Auth/Language/fr-ca/Contributor.php @@ -28,10 +28,16 @@ return [ 'submit_add' => 'Add contributor', 'submit_edit' => 'Update role', ], - 'roles' => [ - 'podcast_admin' => 'Podcast admin', + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', ], 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", 'removeOwnerError' => "You can't remove the podcast owner!", 'removeSuccess' => 'You have successfully removed {username} from {podcastTitle}', diff --git a/modules/Admin/Language/it/MyAccount.php b/modules/Auth/Language/fr-ca/MyAccount.php similarity index 100% rename from modules/Admin/Language/it/MyAccount.php rename to modules/Auth/Language/fr-ca/MyAccount.php diff --git a/modules/Auth/Language/fr-ca/User.php b/modules/Auth/Language/fr-ca/User.php new file mode 100644 index 00000000..32ec560c --- /dev/null +++ b/modules/Auth/Language/fr-ca/User.php @@ -0,0 +1,60 @@ + "Edit {username}'s role", + 'ban' => 'Ban', + 'unban' => 'Unban', + 'delete' => 'Delete', + 'create' => 'New user', + 'view' => "{username}'s info", + 'all_users' => 'All users', + 'list' => [ + 'user' => 'User', + 'role' => 'Role', + 'banned' => 'Banned?', + ], + 'form' => [ + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + 'new_password' => 'New Password', + 'role' => 'Role', + 'roles' => 'Roles', + 'permissions' => 'Permissions', + 'submit_create' => 'Create user', + 'submit_edit' => 'Save', + 'submit_password_change' => 'Change!', + ], + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', + ], + 'messages' => [ + 'createSuccess' => + 'User created successfully! A welcome email was sent to {username} with a login link, they will be prompted with a password reset upon first authentication.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + 'banSuccess' => '{username} has been banned.', + 'unbanSuccess' => '{username} has been unbanned.', + 'editOwnerError' => + '{username} is the instance owner, one does not simply touch the owner…', + 'banSuperAdminError' => + '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', + 'deleteSuperAdminError' => + '{username} is a superadmin, one does not simply delete a superadmin…', + 'deleteSuccess' => '{username} has been deleted.', + ], +]; diff --git a/modules/Auth/Language/fr/Auth.php b/modules/Auth/Language/fr/Auth.php new file mode 100644 index 00000000..4afdf596 --- /dev/null +++ b/modules/Auth/Language/fr/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Propriétaire de l\'instance', + 'description' => 'Le propriétaire du Castopod.', + ], + 'superadmin' => [ + 'title' => 'Super administrat·rice·eur', + 'description' => 'A un contrôle complet sur Castopod.', + ], + 'manager' => [ + 'title' => 'Gestionnaire', + 'description' => 'Gère le contenu de Castopod.', + ], + 'podcaster' => [ + 'title' => 'Podcast·rice·eur', + 'description' => 'Utilisateurs généraux de Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Peut accéder à la zone d\'administration Castopod.', + 'admin.settings' => 'Peut accéder aux paramètres de Castopod.', + 'users.manage' => 'Peut gérer les utilisateurs de Castopod.', + 'persons.manage' => 'Permet de gérer les personnes.', + 'pages.manage' => 'Permet de gérer les pages.', + 'podcasts.view' => 'Peut voir tous les podcasts.', + 'podcasts.create' => 'Peut créer de nouveaux podcasts.', + 'podcasts.import' => 'Peut importer des podcasts.', + 'fediverse.manage-blocks' => 'Peut empêcher des act·rice·eur·s/domaines d\'interagir avec Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Propriétaire du Podcast', + 'description' => 'Le/la propriétaire du podcast.', + ], + 'admin' => [ + 'title' => 'Administrateur', + 'description' => 'A un contrôle total sur le podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Éditeur', + 'description' => 'Gère le contenu et les publications du podcast #{id}.', + ], + 'author' => [ + 'title' => 'Auteur / Autrice', + 'description' => 'Gère le contenu du podcast #{id} , mais ne peut pas le publier.', + ], + 'guest' => [ + 'title' => 'Invité', + 'description' => 'Contributeur général du podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Peut voir le tableau de bord et les analyses du podcast #{id}.', + 'edit' => 'Peut éditer le podcast #{id}.', + 'delete' => 'Peut supprimer le podcast #{id}.', + 'manage-import' => 'Peut synchroniser le podcast importé #{id}.', + 'manage-persons' => 'Permet de gérer les abonnements au podcast #{id}.', + 'manage-subscriptions' => 'Permet de gérer les abonnements au podcast #{id}.', + 'manage-contributors' => 'Permet de gérer les contributeurs du podcast #{id}.', + 'manage-platforms' => 'Peut configurer/supprimer les liens de la plateforme du podcast #{id}.', + 'manage-publications' => 'Peut publier le podcast #{id}.', + 'manage-notifications' => 'Peut afficher et marquer les notifications comme lues pour le podcast #{id}.', + 'interact-as' => 'Peut interagir en tant que podcast #{id} pour mettre en favori, partager ou répondre aux messages.', + 'episodes' => [ + 'view' => 'Peut voir le tableau de bord et les statistiques du podcast #{id}.', + 'create' => 'Peut créer des épisodes pour le podcast #{id}.', + 'edit' => 'Peut modifier les épisodes du podcast #{id}.', + 'delete' => 'Peut supprimer les épisodes du podcast #{id}.', + 'manage-persons' => 'Peut gérer les intervenants des épisodes du podcast #{id}.', + 'manage-clips' => 'Permet de gérer les clips vidéo ou les parties sonores du podcast #{id}.', + 'manage-publications' => 'Peut publier/dépublier des épisodes et des messages de podcast #{id}.', + 'manage-comments' => 'Peut créer/supprimer les commentaires de l\'épisode du podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Votre code à 6 chiffres', + + 'set_password' => 'Choisis ton mot de passe', + + // Welcome email + 'welcomeSubject' => 'Vous avez été invité·e à rejoindre {siteName}', + 'emailWelcomeMailBody' => 'Un compte a été créé pour vous sur {domain}, cliquez sur le lien de connexion ci-dessous pour définir votre mot de passe. Le lien est valide pendant {numberOfHours} heures après l\'envoi de cet e-mail.', +]; diff --git a/modules/Admin/Language/fr/Contributor.php b/modules/Auth/Language/fr/Contributor.php similarity index 71% rename from modules/Admin/Language/fr/Contributor.php rename to modules/Auth/Language/fr/Contributor.php index 700f760b..a9006130 100644 --- a/modules/Admin/Language/fr/Contributor.php +++ b/modules/Auth/Language/fr/Contributor.php @@ -28,10 +28,16 @@ return [ 'submit_add' => 'Ajouter le contributeur', 'submit_edit' => 'Mettre à jour le rôle', ], - 'roles' => [ - 'podcast_admin' => 'Administrateur de Podcasts', + 'delete_form' => [ + 'title' => 'Supprimer {contributor}', + 'disclaimer' => + 'Vous êtes sur le point de supprimer {contributor} des contributeurs. Ils ne pourront plus accéder à "{podcastTitle}".', + 'understand' => 'Je comprends, je veux retirer {contributor} de "{podcastTitle}"', + 'submit' => 'Retirer', ], 'messages' => [ + 'editSuccess' => 'Rôle modifié avec succès !', + 'editOwnerError' => "Vous ne pouvez pas modifier le propriétaire du podcast !", 'removeOwnerError' => "Vous ne pouvez pas retirer le propriétaire du podcast !", 'removeSuccess' => 'Vous avez retiré {username} de {podcastTitle}', diff --git a/modules/Admin/Language/fr/MyAccount.php b/modules/Auth/Language/fr/MyAccount.php similarity index 100% rename from modules/Admin/Language/fr/MyAccount.php rename to modules/Auth/Language/fr/MyAccount.php diff --git a/modules/Admin/Language/fr/User.php b/modules/Auth/Language/fr/User.php similarity index 73% rename from modules/Admin/Language/fr/User.php rename to modules/Auth/Language/fr/User.php index c5d33a12..91139158 100644 --- a/modules/Admin/Language/fr/User.php +++ b/modules/Auth/Language/fr/User.php @@ -9,8 +9,7 @@ declare(strict_types=1); */ return [ - 'edit_roles' => "Modifier les rôles de {username}", - 'forcePassReset' => 'Forcer la réinitialisation du mot de passe', + 'edit_role' => "Modifier le rôle de {username}", 'ban' => 'Bloquer', 'unban' => 'Débloquer', 'delete' => 'Supprimer', @@ -19,7 +18,7 @@ return [ 'all_users' => 'Tous les utilisateurs', 'list' => [ 'user' => 'Utilisateurs', - 'roles' => 'Rôles', + 'role' => 'Rôle', 'banned' => 'Bloqué ?', ], 'form' => [ @@ -27,28 +26,33 @@ return [ 'username' => 'Identifiant', 'password' => 'Mot de passe', 'new_password' => 'Nouveau mot de passe', + 'role' => 'Rôle', 'roles' => 'Rôles', 'permissions' => 'Permissions', 'submit_create' => 'Créer un utilisateur', 'submit_edit' => 'Enregistrer', 'submit_password_change' => 'Valider !', ], - 'roles' => [ - 'superadmin' => 'Super-utilisateur', + 'delete_form' => [ + 'title' => 'Supprimer {user}', + 'disclaimer' => + "Vous êtes sur le point de supprimer {user} définitivement. Ils ne pourront plus accéder à la zone d'administration.", + 'understand' => 'Je comprends, je veux supprimer {user} définitivement', + 'submit' => 'Supprimer', ], 'messages' => [ 'createSuccess' => 'Utilisateur créé avec succès ! {username} devra modifier son mot de passe à la première authentification.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "Les rôles de {username} ont été mis à jour avec succès.", - 'forcePassResetSuccess' => - '{username} devra modifier son mot de passe à la prochaine visite.', 'banSuccess' => '{username} a été bloqué.', 'unbanSuccess' => '{username} a été débloqué.', 'editOwnerError' => '{username} is the instance owner, you cannot edit its roles.', 'banSuperAdminError' => '{username} est un super-utilisateur, on ne bloque pas un super-utilisateur comme ça…', + 'deleteOwnerError' => + '{username} est le propriétaire de l\'instance, on ne supprime pas le propriétaire…', 'deleteSuperAdminError' => '{username} est un super-utilisateur, on ne supprime pas un super-utilisateur comme ça…', 'deleteSuccess' => '{username} a été supprimé.', diff --git a/modules/Auth/Language/fr2/Auth.php b/modules/Auth/Language/fr2/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/fr2/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Auth/Language/fr2/Contributor.php b/modules/Auth/Language/fr2/Contributor.php new file mode 100644 index 00000000..c70badc0 --- /dev/null +++ b/modules/Auth/Language/fr2/Contributor.php @@ -0,0 +1,47 @@ + 'Podcast contributors', + 'view' => "{username}'s contribution to {podcastTitle}", + 'add' => 'Add contributor', + 'add_contributor' => 'Add a contributor for {0}', + 'edit_role' => 'Update role for {0}', + 'edit' => 'Edit', + 'remove' => 'Remove', + 'list' => [ + 'username' => 'Username', + 'role' => 'Role', + ], + 'form' => [ + 'user' => 'User', + 'user_placeholder' => 'Select a user…', + 'role' => 'Role', + 'role_placeholder' => 'Select its role…', + 'submit_add' => 'Add contributor', + 'submit_edit' => 'Update role', + ], + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', + ], + 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", + 'removeOwnerError' => "You can't remove the podcast owner!", + 'removeSuccess' => + 'You have successfully removed {username} from {podcastTitle}', + 'alreadyAddedError' => + "The contributor you're trying to add has already been added!", + ], +]; diff --git a/modules/Admin/Language/nl/MyAccount.php b/modules/Auth/Language/fr2/MyAccount.php similarity index 100% rename from modules/Admin/Language/nl/MyAccount.php rename to modules/Auth/Language/fr2/MyAccount.php diff --git a/modules/Auth/Language/fr2/User.php b/modules/Auth/Language/fr2/User.php new file mode 100644 index 00000000..32ec560c --- /dev/null +++ b/modules/Auth/Language/fr2/User.php @@ -0,0 +1,60 @@ + "Edit {username}'s role", + 'ban' => 'Ban', + 'unban' => 'Unban', + 'delete' => 'Delete', + 'create' => 'New user', + 'view' => "{username}'s info", + 'all_users' => 'All users', + 'list' => [ + 'user' => 'User', + 'role' => 'Role', + 'banned' => 'Banned?', + ], + 'form' => [ + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + 'new_password' => 'New Password', + 'role' => 'Role', + 'roles' => 'Roles', + 'permissions' => 'Permissions', + 'submit_create' => 'Create user', + 'submit_edit' => 'Save', + 'submit_password_change' => 'Change!', + ], + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', + ], + 'messages' => [ + 'createSuccess' => + 'User created successfully! A welcome email was sent to {username} with a login link, they will be prompted with a password reset upon first authentication.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + 'banSuccess' => '{username} has been banned.', + 'unbanSuccess' => '{username} has been unbanned.', + 'editOwnerError' => + '{username} is the instance owner, one does not simply touch the owner…', + 'banSuperAdminError' => + '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', + 'deleteSuperAdminError' => + '{username} is a superadmin, one does not simply delete a superadmin…', + 'deleteSuccess' => '{username} has been deleted.', + ], +]; diff --git a/modules/Auth/Language/gd/Auth.php b/modules/Auth/Language/gd/Auth.php new file mode 100644 index 00000000..2ff1f7d8 --- /dev/null +++ b/modules/Auth/Language/gd/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Sealbhadair an ionstans', + 'description' => 'Cò leis a tha an Castopod seo.', + ], + 'superadmin' => [ + 'title' => 'Sàr-rianaire', + 'description' => 'Smachd gu lèir air Castopod.', + ], + 'manager' => [ + 'title' => 'Manaidsear', + 'description' => 'Stiùireadh susbaint Chastopod.', + ], + 'podcaster' => [ + 'title' => 'Pod-chraoladair', + 'description' => 'Luchd-cleachdaidh coitcheann Chastopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => '’S urrainn dhaibh raon rianachd Chastopod inntrigeadh.', + 'admin.settings' => '’S urrainn dhaibh roghainnean Chastopod inntrigeadh.', + 'users.manage' => '’S urrainn dhaibh luchdc-leachdaidh Chastopod a stiùireadh.', + 'persons.manage' => '’S urrainn dhaibh daoine a stiùireadh.', + 'pages.manage' => '’S urrainn dhaibh duilleagan a stiùireadh.', + 'podcasts.view' => 'Chì iad a h-uile pod-chraoladh.', + 'podcasts.create' => '’S urrainn dhaibh pod-chraolaidhean ùra a chruthachadh.', + 'podcasts.import' => '’S urrainn dhaibh pod-chraolaidhean ion-phortadh.', + 'fediverse.manage-blocks' => '’S urrainn dhaibh actairean/àrainnean a cho-shaoghail a bhacadh o eadar-ghabhail le Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Seilbheadair a’ phod-chraolaidh', + 'description' => 'Cò leis a tha am pod-chraoladh.', + ], + 'admin' => [ + 'title' => 'Rianaire', + 'description' => 'Smachd gu lèir air air a’ phod-chraoladh #{id}.', + ], + 'editor' => [ + 'title' => 'Deasaiche', + 'description' => 'A’ stiùireadh susbaint is foillseachaidhean a’ phod-chraoladh #{id}.', + ], + 'author' => [ + 'title' => 'Ùghdar', + 'description' => 'A’ stiùireadh susbaint a’ phod-chraolaidh #{id} ach gun chomas foillseachaidh.', + ], + 'guest' => [ + 'title' => 'Aoigh', + 'description' => 'Neach-cuideachaidh a’ phod-chraolaidh #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Cead an deas-bhòrd agus anailiseachd a’ phod-chraolaidh #{id} a shealltainn.', + 'edit' => '’S urrainn dhaibh am pod-chraoladh #{id} a dheasachadh.', + 'delete' => '’S urrainn dhaibh am pod-chraoladh #{id} a sguabadh às.', + 'manage-import' => '’S urrainn dhaibh am pod-chraoladh #{id} air ion-phortadh a shioncronachadh.', + 'manage-persons' => '’S urrainn dhaibh na fo-sgrìobhaidhean air a’ phod-chraoladh #{id} a stiùireadh.', + 'manage-subscriptions' => '’S urrainn dhaibh na fo-sgrìobhaidhean air a’ phod-chraoladh #{id} a stiùireadh.', + 'manage-contributors' => '’S urrainn dhaibh an luchd-cuideachaidh aig a’ phod-chraoladh #{id} a stiùireadh.', + 'manage-platforms' => '’S urrainn dhaibh ceanglaichean-ùrlair a’ phod-chraolaidh #{id} a shuidheachadh/a thoirt air falbh.', + 'manage-publications' => '’S urrainn dhaibh am pod-chraoladh #{id} fhoillseachadh.', + 'manage-notifications' => 'Chì iad brathan a’ phod-chraolaidh #{id} agus ’s urrainn dhaibh comharra a chur gun deach an leughadh.', + 'interact-as' => '’S urrainn dhaibh eadar-ghabhail ’na phod-chraoladh #{id} airson annsachdan, co-roinneadh is freagairtean do phostaichean.', + 'episodes' => [ + 'view' => 'Chì iad deas-bhùird is anailiseachd do dh’eapasodan a’ phod-chraolaidh #{id}.', + 'create' => '’S urrainn dhaibh eapasodan a chruthachadh dhan phod-chraoladh #{id}.', + 'edit' => '’S urrainn dhaibh eapasodan a’ phod-chraolaidh #{id} a dheasachadh.', + 'delete' => '’S urrainn dhaibh eapasodan a’ phod-chraolaidh #{id} a sguabadh às.', + 'manage-persons' => '’S urrainn dhaibh daoine nan eapasodan aig a’ phod-chraoladh #{id} a stiùireadh.', + 'manage-clips' => '’S urrainn dhaibh cliopaichean video no blasan-fuaime aig a’ phod-chraoladh #{id} a stiùireadh.', + 'manage-publications' => '’S urrainn dhaibh eapasodan is postaichean a’ phod-chraolaidh #{id} fhoillseachadh/neo-fhoillseachadh.', + 'manage-comments' => '’S urrainn dhaibh beachdan air eapasod a’ phod-chraolaidh #{id} a chruthachadh/a thoirt air falbh.', + ], + ], + + // missing keys + 'code' => 'An còd 6-àireamhach agad', + + 'set_password' => 'Suidhich am facal-faire agad', + + // Welcome email + 'welcomeSubject' => 'Fhuair thu cuireadh gu {siteName}', + 'emailWelcomeMailBody' => 'Chaidh cunntas a chruthachadh dhut air {domain}, briog air ceangal a’ chlàraidh a-steach gu h-ìosal airson am facal-faire agad a shuidheachadh. Bidh an ceangal dligheach fad {numberOfHours} uair a thìde às dèidh cur a’ phuist-d seo.', +]; diff --git a/modules/Auth/Language/gd/Contributor.php b/modules/Auth/Language/gd/Contributor.php new file mode 100644 index 00000000..a6154a55 --- /dev/null +++ b/modules/Auth/Language/gd/Contributor.php @@ -0,0 +1,47 @@ + 'Luchd-cuideachaidh a’ phod-chraolaidh', + 'view' => "Na chuir {username} ri {podcastTitle}", + 'add' => 'Cuir neach-cuideachaidh ris', + 'add_contributor' => 'Cuir neach-cuideachaidh ris airson {0}', + 'edit_role' => 'Ùraich an dreuchd airson {0}', + 'edit' => 'Deasaich', + 'remove' => 'Thoir air falbh', + 'list' => [ + 'username' => 'Ainm-cleachdaiche', + 'role' => 'Dreuchd', + ], + 'form' => [ + 'user' => 'Cleachdaiche', + 'user_placeholder' => 'Tagh cleachdaiche…', + 'role' => 'Dreuchd', + 'role_placeholder' => 'Tagh dreuchd dhaibh…', + 'submit_add' => 'Cuir neach-cuideachaidh ris', + 'submit_edit' => 'Ùraich an dreuchd', + ], + 'delete_form' => [ + 'title' => 'Thoir {contributor} air falbh', + 'disclaimer' => + 'Tha thu an impis {contributor} a toirt air falbh on luchd-cuideachaidh. Chan urrainn dhaibh “{podcastTitle}” inntrigeadh tuilleadh an uairsin.', + 'understand' => 'Tha mi agaibh, tha mi airson {contributor} a thoirt air falbh o “{podcastTitle}”', + 'submit' => 'Thoir air falbh', + ], + 'messages' => [ + 'editSuccess' => 'Chaidh an dreuchd atharrachadh!', + 'editOwnerError' => "Chan urrainn dhut sealbhadair a’ phod-chraolaidh a dheasachadh!", + 'removeOwnerError' => "Chan urrainn dhut sealbhadair a’ phod-chraolaidh a thoirt air falbh!", + 'removeSuccess' => + 'Thug thu {username} air falbh o {podcastTitle}', + 'alreadyAddedError' => + "Chaidh an neach-cuideachaidh a tha thu airson cur ris a chur ris mu thràth!", + ], +]; diff --git a/modules/Auth/Language/gd/MyAccount.php b/modules/Auth/Language/gd/MyAccount.php new file mode 100644 index 00000000..1a9f8fe1 --- /dev/null +++ b/modules/Auth/Language/gd/MyAccount.php @@ -0,0 +1,18 @@ + 'Fiosrachadh a’ chunntais agam', + 'changePassword' => 'Atharraich am facal-faire agam', + 'messages' => [ + 'wrongPasswordError' => "Chuir thu a-steach am facal-faire ceàrr, feuch ris a-rithist.", + 'passwordChangeSuccess' => 'Chaidh am facal-faire atharrachadh!', + ], +]; diff --git a/modules/Auth/Language/gd/User.php b/modules/Auth/Language/gd/User.php new file mode 100644 index 00000000..33a1d513 --- /dev/null +++ b/modules/Auth/Language/gd/User.php @@ -0,0 +1,60 @@ + "Deasaich an dreuchd aig {username}", + 'ban' => 'Toirmisg', + 'unban' => 'Dì-thoirmisg', + 'delete' => 'Sguab às', + 'create' => 'Cleachdaiche ùr', + 'view' => "Am fiosrachadh aig {username}", + 'all_users' => 'A h-uile cleachdaiche', + 'list' => [ + 'user' => 'Cleachdaiche', + 'role' => 'Dreuchd', + 'banned' => 'Air a thoirmeasg?', + ], + 'form' => [ + 'email' => 'Post-d', + 'username' => 'Ainm-cleachdaiche', + 'password' => 'Facal-faire', + 'new_password' => 'Am facal-faire ùr', + 'role' => 'Dreuchd', + 'roles' => 'Dreuchdan', + 'permissions' => 'Ceadan', + 'submit_create' => 'Cruthaich cleachdaiche', + 'submit_edit' => 'Sàbhail', + 'submit_password_change' => 'Atharraich!', + ], + 'delete_form' => [ + 'title' => 'Sguab às {user}', + 'disclaimer' => + "Tha thu an impis {user} a sguabadh às gu buan. Chan urrainn dhaibh raon na rianachd inntrigeadh tuilleadh an uairsin.", + 'understand' => 'Tha mi agaibh, tha mi airson {user} a sguabadh às gu buan', + 'submit' => 'Sguab às', + ], + 'messages' => [ + 'createSuccess' => + 'Chaidh an cleachdaiche a chruthachadh! Chaidh post-d fàilteachaidh a chur gu {username} le ceangal clàraidh a-steach, thèid iarraidh orra gun ath-shuidhich iad am facal-faire aca a’ chiad turas a nì iad dearbhadh.', + 'roleEditSuccess' => + "Chaidh na dreuchdan aig {username} ùrachadh.", + 'banSuccess' => 'Chaidh {username} a thoirmeasg.', + 'unbanSuccess' => 'Chaidh {username} a dhì-thoirmeasg.', + 'editOwnerError' => + 'Is {username} sealbhadair an ionstans, na bean ris an t-sealbhadair…', + 'banSuperAdminError' => + 'Tha {username} ’na shàr-rianaire, na toirmisg sàr-rianaire…', + 'deleteOwnerError' => + 'Is {username} sealbhadair an ionstans, na sguab às an sealbhadair…', + 'deleteSuperAdminError' => + 'Tha {username} ’na shàr-rianaire, na sguab às sàr-rianaire…', + 'deleteSuccess' => 'Chaidh {username} a sguabadh às.', + ], +]; diff --git a/modules/Auth/Language/gl/Auth.php b/modules/Auth/Language/gl/Auth.php new file mode 100644 index 00000000..15902033 --- /dev/null +++ b/modules/Auth/Language/gl/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Propietaria da instancia', + 'description' => 'Propietaria de Castopod.', + ], + 'superadmin' => [ + 'title' => 'Super Admin', + 'description' => 'Ten control completo sobre Castopod.', + ], + 'manager' => [ + 'title' => 'Xestora', + 'description' => 'Quen xestiona o contido de Castopod.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'Usuaria común de Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Pode acceder á área de administración.', + 'admin.settings' => 'Pode acceder aos axustes de Castopod.', + 'users.manage' => 'Pode xestionar as usuarias de Castopod.', + 'persons.manage' => 'Pode xestionar persoas.', + 'pages.manage' => 'Pode xestionar páxinas.', + 'podcasts.view' => 'Pode ver tódolos podcast.', + 'podcasts.create' => 'Pode crear novos podcast.', + 'podcasts.import' => 'Pode importar podcasts.', + 'fediverse.manage-blocks' => 'Pode bloquear actores/dominios do fediverso evitando interactuar con Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Dona do Podcast', + 'description' => 'A propietaria do podcast.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Ten control total sobre o podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editora', + 'description' => 'Persoa que xestiona o contido e publicacións do podcast #{id}.', + ], + 'author' => [ + 'title' => 'Autora', + 'description' => 'Persoa que xestiona o contido do podcast #{id} pero non pode publicalo.', + ], + 'guest' => [ + 'title' => 'Convidada', + 'description' => 'Contribuínte básico ao podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Pode ver o taboleiro e estatísticas do podcast #{id}.', + 'edit' => 'Pode editar o podcast #{id}.', + 'delete' => 'Pode eliminar o podcast #{id}.', + 'manage-import' => 'Pode sincronizar o podcast importado #{id}.', + 'manage-persons' => 'Pode xestionar as subscricións do podcast #{id}.', + 'manage-subscriptions' => 'Pode xestionar as subscricións do podcast #{id}.', + 'manage-contributors' => 'Pode xestionar as contribucións ao podcast #{id}.', + 'manage-platforms' => 'Pode establecer/eliminar ligazóns a plataformas do podcast #{id}.', + 'manage-publications' => 'Pode publicar o podcast #{id}.', + 'manage-notifications' => 'Pode ver e marcar as notificacións como lidas no podcast #{id}.', + 'interact-as' => 'Pode actuar como o podcast #{id} para compartir, favorecer ou responder a publicacións.', + 'episodes' => [ + 'view' => 'Pode ver os taboleiros e estatísticas dos episodios do podcast #{id}.', + 'create' => 'Pode crear episodios para o podcast #{id}.', + 'edit' => 'Pode editar os episodios do podcast #{id}.', + 'delete' => 'Pode eliminar episodios do podcast #{id}.', + 'manage-persons' => 'Pode xestionar as persoas do episodio do podcast #{id}.', + 'manage-clips' => 'Pode xestionar os clips de vídeo e extractos de audio do podcast #{id}.', + 'manage-publications' => 'Pode publicar/retirar episodios e publicacións do podcast #{id}.', + 'manage-comments' => 'Pode crear/eliminar comentarios dos episodios do podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Código de 6 díxitos', + + 'set_password' => 'Establece un contrasinal', + + // Welcome email + 'welcomeSubject' => 'Recibiches un convite para {siteName}', + 'emailWelcomeMailBody' => 'Creouse unha conta para ti en {domain}, preme na ligazón inferior de acceso para establecer un contrasinal. A ligazón é válida durante {numberOfHours} horas desde que se enviou este email.', +]; diff --git a/modules/Admin/Language/gl/Contributor.php b/modules/Auth/Language/gl/Contributor.php similarity index 71% rename from modules/Admin/Language/gl/Contributor.php rename to modules/Auth/Language/gl/Contributor.php index a09021cd..14dcf288 100644 --- a/modules/Admin/Language/gl/Contributor.php +++ b/modules/Auth/Language/gl/Contributor.php @@ -28,10 +28,16 @@ return [ 'submit_add' => 'Engadir colaboración', 'submit_edit' => 'Actualizar rol', ], - 'roles' => [ - 'podcast_admin' => 'Admin podcast', + 'delete_form' => [ + 'title' => 'Eliminar {contributor}', + 'disclaimer' => + 'Vas a eliminar a {contributor} de entre as contribuíntes. Non poderá volver acceder a "{podcastTitle}".', + 'understand' => 'Enténdoo, quero eliminar a {contributor} de "{podcastTitle}"', + 'submit' => 'Eliminar', ], 'messages' => [ + 'editSuccess' => 'Rol cambiado correctamente!', + 'editOwnerError' => "Non podes editar a propietaria do podcast!", 'removeOwnerError' => "Non podes eliminar a propietaria do podcast!", 'removeSuccess' => 'Quitaches correctamente a {username} de {podcastTitle}', diff --git a/modules/Admin/Language/gl/MyAccount.php b/modules/Auth/Language/gl/MyAccount.php similarity index 100% rename from modules/Admin/Language/gl/MyAccount.php rename to modules/Auth/Language/gl/MyAccount.php diff --git a/modules/Admin/Language/gl/User.php b/modules/Auth/Language/gl/User.php similarity index 73% rename from modules/Admin/Language/gl/User.php rename to modules/Auth/Language/gl/User.php index 4bdfcab9..668d56a5 100644 --- a/modules/Admin/Language/gl/User.php +++ b/modules/Auth/Language/gl/User.php @@ -9,8 +9,7 @@ declare(strict_types=1); */ return [ - 'edit_roles' => "Editar os roles de {username}", - 'forcePassReset' => 'Forzar restablecemento do contrasinal', + 'edit_role' => "Editar os roles de {username}", 'ban' => 'Vetar', 'unban' => 'Retirar veto', 'delete' => 'Eliminar', @@ -19,7 +18,7 @@ return [ 'all_users' => 'Tódalas usuarias', 'list' => [ 'user' => 'Usuaria', - 'roles' => 'Roles', + 'role' => 'Rol', 'banned' => 'Vetada?', ], 'form' => [ @@ -27,28 +26,33 @@ return [ 'username' => 'Identificador', 'password' => 'Contrasinal', 'new_password' => 'Novo contrasinal', + 'role' => 'Rol', 'roles' => 'Roles', 'permissions' => 'Permisos', 'submit_create' => 'Crear usuaria', 'submit_edit' => 'Gardar', 'submit_password_change' => 'Cambiar!', ], - 'roles' => [ - 'superadmin' => 'Super admin', + 'delete_form' => [ + 'title' => 'Eliminar {user}', + 'disclaimer' => + "Vas eliminar de xeito permanente a {user}. Non poderá volver a acceder á páxina de administración.", + 'understand' => 'Enténdoo, quero eliminar a {user} para sempre', + 'submit' => 'Eliminar', ], 'messages' => [ 'createSuccess' => 'Usuaria creada correctamente! Váiselle pedir a {username} que cambie o seu contrasinal após o primeiro acceso.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "Os roles de {username} actualizáronse correctamente.", - 'forcePassResetSuccess' => - 'Solicitarase que {username} restableza o contrasinal na seguinte visita.', 'banSuccess' => '{username} foi vetada.', 'unbanSuccess' => 'Retirouse o veto sobre {username}.', 'editOwnerError' => 'A instancia pertence a {username}, ti non podes editar os roles.', 'banSuperAdminError' => '{username} é superadmin, non se pode vetar a superadmin…', + 'deleteOwnerError' => + '{username} é dona da instancia, non se pode eliminar a propietaria…', 'deleteSuperAdminError' => '{username} é superadmin, non se pode eliminar a superadmin…', 'deleteSuccess' => 'Eliminouse a {username}.', diff --git a/modules/Auth/Language/id/Auth.php b/modules/Auth/Language/id/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/id/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Auth/Language/id/Contributor.php b/modules/Auth/Language/id/Contributor.php new file mode 100644 index 00000000..c70badc0 --- /dev/null +++ b/modules/Auth/Language/id/Contributor.php @@ -0,0 +1,47 @@ + 'Podcast contributors', + 'view' => "{username}'s contribution to {podcastTitle}", + 'add' => 'Add contributor', + 'add_contributor' => 'Add a contributor for {0}', + 'edit_role' => 'Update role for {0}', + 'edit' => 'Edit', + 'remove' => 'Remove', + 'list' => [ + 'username' => 'Username', + 'role' => 'Role', + ], + 'form' => [ + 'user' => 'User', + 'user_placeholder' => 'Select a user…', + 'role' => 'Role', + 'role_placeholder' => 'Select its role…', + 'submit_add' => 'Add contributor', + 'submit_edit' => 'Update role', + ], + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', + ], + 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", + 'removeOwnerError' => "You can't remove the podcast owner!", + 'removeSuccess' => + 'You have successfully removed {username} from {podcastTitle}', + 'alreadyAddedError' => + "The contributor you're trying to add has already been added!", + ], +]; diff --git a/modules/Admin/Language/oc/MyAccount.php b/modules/Auth/Language/id/MyAccount.php similarity index 100% rename from modules/Admin/Language/oc/MyAccount.php rename to modules/Auth/Language/id/MyAccount.php diff --git a/modules/Admin/Language/gd/User.php b/modules/Auth/Language/id/User.php similarity index 73% rename from modules/Admin/Language/gd/User.php rename to modules/Auth/Language/id/User.php index 585d6799..e7908f5b 100644 --- a/modules/Admin/Language/gd/User.php +++ b/modules/Auth/Language/id/User.php @@ -9,8 +9,7 @@ declare(strict_types=1); */ return [ - 'edit_roles' => "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', + 'edit_role' => "Edit {username}'s role", 'ban' => 'Ban', 'unban' => 'Unban', 'delete' => 'Delete', @@ -19,7 +18,7 @@ return [ 'all_users' => 'All users', 'list' => [ 'user' => 'User', - 'roles' => 'Roles', + 'role' => 'Role', 'banned' => 'Banned?', ], 'form' => [ @@ -27,28 +26,33 @@ return [ 'username' => 'Username', 'password' => 'Password', 'new_password' => 'New Password', + 'role' => 'Role', 'roles' => 'Roles', 'permissions' => 'Permissions', 'submit_create' => 'Create user', 'submit_edit' => 'Save', 'submit_password_change' => 'Change!', ], - 'roles' => [ - 'superadmin' => 'Super admin', + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', ], 'messages' => [ 'createSuccess' => 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "{username}'s roles have been successfully updated.", - 'forcePassResetSuccess' => - '{username} will be prompted with a password reset upon next visit.', 'banSuccess' => '{username} has been banned.', 'unbanSuccess' => '{username} has been unbanned.', 'editOwnerError' => '{username} is the instance owner, you cannot edit its roles.', 'banSuperAdminError' => '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', 'deleteSuperAdminError' => '{username} is a superadmin, one does not simply delete a superadmin…', 'deleteSuccess' => '{username} has been deleted.', diff --git a/modules/Auth/Language/it/Auth.php b/modules/Auth/Language/it/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/it/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Admin/Language/it/Contributor.php b/modules/Auth/Language/it/Contributor.php similarity index 72% rename from modules/Admin/Language/it/Contributor.php rename to modules/Auth/Language/it/Contributor.php index ab685c04..b82b51bb 100644 --- a/modules/Admin/Language/it/Contributor.php +++ b/modules/Auth/Language/it/Contributor.php @@ -28,10 +28,16 @@ return [ 'submit_add' => 'Aggiungi collaboratore', 'submit_edit' => 'Aggiorna Ruolo', ], - 'roles' => [ - 'podcast_admin' => 'Amministratore del podcast', + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', ], 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", 'removeOwnerError' => "Non puoi rimuovere il proprietario del podcast!", 'removeSuccess' => 'Hai rimosso con successo {username} da {podcastTitle}', diff --git a/modules/Admin/Language/pt/MyAccount.php b/modules/Auth/Language/it/MyAccount.php similarity index 100% rename from modules/Admin/Language/pt/MyAccount.php rename to modules/Auth/Language/it/MyAccount.php diff --git a/modules/Admin/Language/el/User.php b/modules/Auth/Language/it/User.php similarity index 73% rename from modules/Admin/Language/el/User.php rename to modules/Auth/Language/it/User.php index 585d6799..e7908f5b 100644 --- a/modules/Admin/Language/el/User.php +++ b/modules/Auth/Language/it/User.php @@ -9,8 +9,7 @@ declare(strict_types=1); */ return [ - 'edit_roles' => "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', + 'edit_role' => "Edit {username}'s role", 'ban' => 'Ban', 'unban' => 'Unban', 'delete' => 'Delete', @@ -19,7 +18,7 @@ return [ 'all_users' => 'All users', 'list' => [ 'user' => 'User', - 'roles' => 'Roles', + 'role' => 'Role', 'banned' => 'Banned?', ], 'form' => [ @@ -27,28 +26,33 @@ return [ 'username' => 'Username', 'password' => 'Password', 'new_password' => 'New Password', + 'role' => 'Role', 'roles' => 'Roles', 'permissions' => 'Permissions', 'submit_create' => 'Create user', 'submit_edit' => 'Save', 'submit_password_change' => 'Change!', ], - 'roles' => [ - 'superadmin' => 'Super admin', + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', ], 'messages' => [ 'createSuccess' => 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "{username}'s roles have been successfully updated.", - 'forcePassResetSuccess' => - '{username} will be prompted with a password reset upon next visit.', 'banSuccess' => '{username} has been banned.', 'unbanSuccess' => '{username} has been unbanned.', 'editOwnerError' => '{username} is the instance owner, you cannot edit its roles.', 'banSuperAdminError' => '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', 'deleteSuperAdminError' => '{username} is a superadmin, one does not simply delete a superadmin…', 'deleteSuccess' => '{username} has been deleted.', diff --git a/modules/Auth/Language/ja/Auth.php b/modules/Auth/Language/ja/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/ja/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Auth/Language/ja/Contributor.php b/modules/Auth/Language/ja/Contributor.php new file mode 100644 index 00000000..c70badc0 --- /dev/null +++ b/modules/Auth/Language/ja/Contributor.php @@ -0,0 +1,47 @@ + 'Podcast contributors', + 'view' => "{username}'s contribution to {podcastTitle}", + 'add' => 'Add contributor', + 'add_contributor' => 'Add a contributor for {0}', + 'edit_role' => 'Update role for {0}', + 'edit' => 'Edit', + 'remove' => 'Remove', + 'list' => [ + 'username' => 'Username', + 'role' => 'Role', + ], + 'form' => [ + 'user' => 'User', + 'user_placeholder' => 'Select a user…', + 'role' => 'Role', + 'role_placeholder' => 'Select its role…', + 'submit_add' => 'Add contributor', + 'submit_edit' => 'Update role', + ], + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', + ], + 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", + 'removeOwnerError' => "You can't remove the podcast owner!", + 'removeSuccess' => + 'You have successfully removed {username} from {podcastTitle}', + 'alreadyAddedError' => + "The contributor you're trying to add has already been added!", + ], +]; diff --git a/modules/Admin/Language/ru/MyAccount.php b/modules/Auth/Language/ja/MyAccount.php similarity index 100% rename from modules/Admin/Language/ru/MyAccount.php rename to modules/Auth/Language/ja/MyAccount.php diff --git a/modules/Auth/Language/ja/User.php b/modules/Auth/Language/ja/User.php new file mode 100644 index 00000000..32ec560c --- /dev/null +++ b/modules/Auth/Language/ja/User.php @@ -0,0 +1,60 @@ + "Edit {username}'s role", + 'ban' => 'Ban', + 'unban' => 'Unban', + 'delete' => 'Delete', + 'create' => 'New user', + 'view' => "{username}'s info", + 'all_users' => 'All users', + 'list' => [ + 'user' => 'User', + 'role' => 'Role', + 'banned' => 'Banned?', + ], + 'form' => [ + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + 'new_password' => 'New Password', + 'role' => 'Role', + 'roles' => 'Roles', + 'permissions' => 'Permissions', + 'submit_create' => 'Create user', + 'submit_edit' => 'Save', + 'submit_password_change' => 'Change!', + ], + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', + ], + 'messages' => [ + 'createSuccess' => + 'User created successfully! A welcome email was sent to {username} with a login link, they will be prompted with a password reset upon first authentication.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + 'banSuccess' => '{username} has been banned.', + 'unbanSuccess' => '{username} has been unbanned.', + 'editOwnerError' => + '{username} is the instance owner, one does not simply touch the owner…', + 'banSuperAdminError' => + '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', + 'deleteSuperAdminError' => + '{username} is a superadmin, one does not simply delete a superadmin…', + 'deleteSuccess' => '{username} has been deleted.', + ], +]; diff --git a/modules/Auth/Language/kk/Auth.php b/modules/Auth/Language/kk/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/kk/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Auth/Language/kk/Contributor.php b/modules/Auth/Language/kk/Contributor.php new file mode 100644 index 00000000..c70badc0 --- /dev/null +++ b/modules/Auth/Language/kk/Contributor.php @@ -0,0 +1,47 @@ + 'Podcast contributors', + 'view' => "{username}'s contribution to {podcastTitle}", + 'add' => 'Add contributor', + 'add_contributor' => 'Add a contributor for {0}', + 'edit_role' => 'Update role for {0}', + 'edit' => 'Edit', + 'remove' => 'Remove', + 'list' => [ + 'username' => 'Username', + 'role' => 'Role', + ], + 'form' => [ + 'user' => 'User', + 'user_placeholder' => 'Select a user…', + 'role' => 'Role', + 'role_placeholder' => 'Select its role…', + 'submit_add' => 'Add contributor', + 'submit_edit' => 'Update role', + ], + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', + ], + 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", + 'removeOwnerError' => "You can't remove the podcast owner!", + 'removeSuccess' => + 'You have successfully removed {username} from {podcastTitle}', + 'alreadyAddedError' => + "The contributor you're trying to add has already been added!", + ], +]; diff --git a/modules/Admin/Language/sv/MyAccount.php b/modules/Auth/Language/kk/MyAccount.php similarity index 100% rename from modules/Admin/Language/sv/MyAccount.php rename to modules/Auth/Language/kk/MyAccount.php diff --git a/modules/Auth/Language/kk/User.php b/modules/Auth/Language/kk/User.php new file mode 100644 index 00000000..32ec560c --- /dev/null +++ b/modules/Auth/Language/kk/User.php @@ -0,0 +1,60 @@ + "Edit {username}'s role", + 'ban' => 'Ban', + 'unban' => 'Unban', + 'delete' => 'Delete', + 'create' => 'New user', + 'view' => "{username}'s info", + 'all_users' => 'All users', + 'list' => [ + 'user' => 'User', + 'role' => 'Role', + 'banned' => 'Banned?', + ], + 'form' => [ + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + 'new_password' => 'New Password', + 'role' => 'Role', + 'roles' => 'Roles', + 'permissions' => 'Permissions', + 'submit_create' => 'Create user', + 'submit_edit' => 'Save', + 'submit_password_change' => 'Change!', + ], + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', + ], + 'messages' => [ + 'createSuccess' => + 'User created successfully! A welcome email was sent to {username} with a login link, they will be prompted with a password reset upon first authentication.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + 'banSuccess' => '{username} has been banned.', + 'unbanSuccess' => '{username} has been unbanned.', + 'editOwnerError' => + '{username} is the instance owner, one does not simply touch the owner…', + 'banSuperAdminError' => + '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', + 'deleteSuperAdminError' => + '{username} is a superadmin, one does not simply delete a superadmin…', + 'deleteSuccess' => '{username} has been deleted.', + ], +]; diff --git a/modules/Auth/Language/ko/Auth.php b/modules/Auth/Language/ko/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/ko/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Auth/Language/ko/Contributor.php b/modules/Auth/Language/ko/Contributor.php new file mode 100644 index 00000000..c70badc0 --- /dev/null +++ b/modules/Auth/Language/ko/Contributor.php @@ -0,0 +1,47 @@ + 'Podcast contributors', + 'view' => "{username}'s contribution to {podcastTitle}", + 'add' => 'Add contributor', + 'add_contributor' => 'Add a contributor for {0}', + 'edit_role' => 'Update role for {0}', + 'edit' => 'Edit', + 'remove' => 'Remove', + 'list' => [ + 'username' => 'Username', + 'role' => 'Role', + ], + 'form' => [ + 'user' => 'User', + 'user_placeholder' => 'Select a user…', + 'role' => 'Role', + 'role_placeholder' => 'Select its role…', + 'submit_add' => 'Add contributor', + 'submit_edit' => 'Update role', + ], + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', + ], + 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", + 'removeOwnerError' => "You can't remove the podcast owner!", + 'removeSuccess' => + 'You have successfully removed {username} from {podcastTitle}', + 'alreadyAddedError' => + "The contributor you're trying to add has already been added!", + ], +]; diff --git a/modules/Auth/Language/ko/MyAccount.php b/modules/Auth/Language/ko/MyAccount.php new file mode 100644 index 00000000..6ebbb30e --- /dev/null +++ b/modules/Auth/Language/ko/MyAccount.php @@ -0,0 +1,18 @@ + 'My account info', + 'changePassword' => 'Change my password', + 'messages' => [ + 'wrongPasswordError' => "You've entered the wrong password, try again.", + 'passwordChangeSuccess' => 'Password has been successfully changed!', + ], +]; diff --git a/modules/Auth/Language/ko/User.php b/modules/Auth/Language/ko/User.php new file mode 100644 index 00000000..32ec560c --- /dev/null +++ b/modules/Auth/Language/ko/User.php @@ -0,0 +1,60 @@ + "Edit {username}'s role", + 'ban' => 'Ban', + 'unban' => 'Unban', + 'delete' => 'Delete', + 'create' => 'New user', + 'view' => "{username}'s info", + 'all_users' => 'All users', + 'list' => [ + 'user' => 'User', + 'role' => 'Role', + 'banned' => 'Banned?', + ], + 'form' => [ + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + 'new_password' => 'New Password', + 'role' => 'Role', + 'roles' => 'Roles', + 'permissions' => 'Permissions', + 'submit_create' => 'Create user', + 'submit_edit' => 'Save', + 'submit_password_change' => 'Change!', + ], + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', + ], + 'messages' => [ + 'createSuccess' => + 'User created successfully! A welcome email was sent to {username} with a login link, they will be prompted with a password reset upon first authentication.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + 'banSuccess' => '{username} has been banned.', + 'unbanSuccess' => '{username} has been unbanned.', + 'editOwnerError' => + '{username} is the instance owner, one does not simply touch the owner…', + 'banSuperAdminError' => + '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', + 'deleteSuperAdminError' => + '{username} is a superadmin, one does not simply delete a superadmin…', + 'deleteSuccess' => '{username} has been deleted.', + ], +]; diff --git a/modules/Auth/Language/nl/Auth.php b/modules/Auth/Language/nl/Auth.php new file mode 100644 index 00000000..31521984 --- /dev/null +++ b/modules/Auth/Language/nl/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance eigenaar', + 'description' => 'De Castopod eigenaar.', + ], + 'superadmin' => [ + 'title' => 'Super beheerder', + 'description' => 'Heeft de volledige controle over Castopod.', + ], + 'manager' => [ + 'title' => 'Beheerder', + 'description' => 'Beheert de inhoud van Castopod.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'Algemene gebruikers van Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Kan toegang krijgen tot de beheeromgeving van Castopod.', + 'admin.settings' => 'Kan toegang krijgen tot de instellingen van Castopod.', + 'users.manage' => 'Kan Castopod-gebruikers beheren.', + 'persons.manage' => 'Kan personen beheren.', + 'pages.manage' => 'Kan pagina\'s beheren.', + 'podcasts.view' => 'Kan alle podcasts bekijken.', + 'podcasts.create' => 'Kan nieuwe podcast aanmaken.', + 'podcasts.import' => 'Kan podcasts importeren.', + 'fediverse.manage-blocks' => 'Kan fediverse actors/domains blokkeren voor interactie met Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Eigenaar', + 'description' => 'De eigenaar van de podcast.', + ], + 'admin' => [ + 'title' => 'Beheerder', + 'description' => 'Heeft de volledige controle over podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Redacteur', + 'description' => 'Beheert inhoud en publicaties van podcast #{id}.', + ], + 'author' => [ + 'title' => 'Auteur', + 'description' => 'Beheert de inhoud van podcast #{id} maar kan deze niet publiceren.', + ], + 'guest' => [ + 'title' => 'Gast', + 'description' => 'Algemene bijdrager van podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Kan dashboard en analyses van podcast #{id} zien.', + 'edit' => 'Kan podcast #{id} wijzigen.', + 'delete' => 'Kan podcast #{id} verwijderen.', + 'manage-import' => 'Kan de geïmporteerde podcast #{id} synchroniseren.', + 'manage-persons' => 'Kan abonnementen van podcast #{id} beheren.', + 'manage-subscriptions' => 'Kan abonnementen van podcast #{id} beheren.', + 'manage-contributors' => 'Kan bijdragers van podcast #{id} beheren.', + 'manage-platforms' => 'Kan platform links van podcast #{id} instellen of verwijderen.', + 'manage-publications' => 'Kan podcast #{id} publiceren.', + 'manage-notifications' => 'Kan meldingen bekijken en markeren als gelezen voor podcast #{id}.', + 'interact-as' => 'Kan als podcast #{id} handelen om te favorieten, te delen of te reageren op berichten.', + 'episodes' => [ + 'view' => 'Kan dashboard en analyses van de afleveringen van podcast #{id} zien.', + 'create' => 'Kan afleveringen voor podcast #{id} aanmaken.', + 'edit' => 'Kan afleveringen van podcast #{id} wijzigen.', + 'delete' => 'Kan afleveringen van podcast #{id} verwijderen.', + 'manage-persons' => 'Kan aflevering personen van podcast #{id} beheren.', + 'manage-clips' => 'Kan videoclips of soundbites van podcast #{id} beheren.', + 'manage-publications' => 'Kan afleveringen en berichten van podcast #{id} publiceren/depubliceren.', + 'manage-comments' => 'Kan opmerkingen van aflevering van podcast van #{id} maken of verwijderen.', + ], + ], + + // missing keys + 'code' => 'Jouw 6-cijferige code', + + 'set_password' => 'Stel je wachtwoord in', + + // Welcome email + 'welcomeSubject' => 'Je bent uitgenodigd voor {siteName}', + 'emailWelcomeMailBody' => 'Er is een account voor u aangemaakt op {domain}, klik op onderstaande inloglink om uw wachtwoord in te stellen. De link is geldig tot {numberOfHours} uur nadat deze e-mail is verzonden.', +]; diff --git a/modules/Admin/Language/nl/Contributor.php b/modules/Auth/Language/nl/Contributor.php similarity index 70% rename from modules/Admin/Language/nl/Contributor.php rename to modules/Auth/Language/nl/Contributor.php index a9d938d6..f6b11364 100644 --- a/modules/Admin/Language/nl/Contributor.php +++ b/modules/Auth/Language/nl/Contributor.php @@ -28,10 +28,16 @@ return [ 'submit_add' => 'Voeg bijdrager toe', 'submit_edit' => 'Rol bijwerken', ], - 'roles' => [ - 'podcast_admin' => 'Podcast beheerder', + 'delete_form' => [ + 'title' => 'Verwijder {contributor}', + 'disclaimer' => + 'Je staat op het punt {contributor} te verwijderen van bijdragers. Ze zullen geen toegang meer hebben tot "{podcastTitle}".', + 'understand' => 'Ik begrijp het, ik wil {contributor} verwijderen van "{podcastTitle}"', + 'submit' => 'Verwijder', ], 'messages' => [ + 'editSuccess' => 'Rol succesvol veranderd!', + 'editOwnerError' => "Je kunt de eigenaar van podcast niet bewerken!", 'removeOwnerError' => "Je kunt de eigenaar van podcast niet verwijderen!", 'removeSuccess' => 'Je hebt {username} met succes verwijderd van {podcastTitle}', diff --git a/modules/Auth/Language/nl/MyAccount.php b/modules/Auth/Language/nl/MyAccount.php new file mode 100644 index 00000000..c7a5d5cf --- /dev/null +++ b/modules/Auth/Language/nl/MyAccount.php @@ -0,0 +1,18 @@ + 'Mijn accountgegevens', + 'changePassword' => 'Wijzig mijn wachtwoord', + 'messages' => [ + 'wrongPasswordError' => "Je hebt een verkeerd wachtwoord ingevoerd, probeer het opnieuw.", + 'passwordChangeSuccess' => 'Wachtwoord is succesvol gewijzigd!', + ], +]; diff --git a/modules/Auth/Language/nl/User.php b/modules/Auth/Language/nl/User.php new file mode 100644 index 00000000..ee321721 --- /dev/null +++ b/modules/Auth/Language/nl/User.php @@ -0,0 +1,60 @@ + "Rol van {username} wijzigen", + 'ban' => 'Blokkeren', + 'unban' => 'Deblokkeren', + 'delete' => 'Verwijderen', + 'create' => 'Nieuwe gebruiker', + 'view' => "Info van {username}", + 'all_users' => 'Alle gebruikers', + 'list' => [ + 'user' => 'Gebruiker', + 'role' => 'Rol', + 'banned' => 'Geblokkeerd?', + ], + 'form' => [ + 'email' => 'E-mail', + 'username' => 'Gebruikersnaam', + 'password' => 'Wachtwoord', + 'new_password' => 'Nieuw Wachtwoord', + 'role' => 'Rol', + 'roles' => 'Rollen', + 'permissions' => 'Rechten', + 'submit_create' => 'Gebruiker aanmaken', + 'submit_edit' => 'Opslaan', + 'submit_password_change' => 'Wijzigen!', + ], + 'delete_form' => [ + 'title' => 'Verwijder {user}', + 'disclaimer' => + "Je staat op het punt {user} permanent te verwijderen. Deze zal geen toegang meer hebben tot de beheerdersomgeving.", + 'understand' => 'Ik begrijp het, ik wil {user} permanent verwijderen', + 'submit' => 'Verwijderen', + ], + 'messages' => [ + 'createSuccess' => + 'Gebruiker succesvol aangemaakt! Een welkomsmail is naar {username} verzonden met een inloglink, bij de eerste authenticatie zal er om een wachtwoordreset gevraagd worden.', + 'roleEditSuccess' => + "De rollen van {username} zijn succesvol bijgewerkt.", + 'banSuccess' => '{username} is geblokkeerd.', + 'unbanSuccess' => '{username} is gedeblokkeerd.', + 'editOwnerError' => + '{username} is de instance eigenaar, men raakt niet zomaar de eigenaar aan…', + 'banSuperAdminError' => + '{username} is een super beheerder, men raakt niet zomaar een super beheerder aan…', + 'deleteOwnerError' => + '{username} is de instance eigenaar, men verwijderd niet zomaar de eigenaar…', + 'deleteSuperAdminError' => + '{username} is een superadmin, men verwijderd niet zomaar een superadmin…', + 'deleteSuccess' => '{username} is verwijderd.', + ], +]; diff --git a/modules/Auth/Language/nn-no/Auth.php b/modules/Auth/Language/nn-no/Auth.php new file mode 100644 index 00000000..ba1c57f9 --- /dev/null +++ b/modules/Auth/Language/nn-no/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Nettstadeigar', + 'description' => 'Castopod-eigaren.', + ], + 'superadmin' => [ + 'title' => 'Superstyrar', + 'description' => 'Har full kontroll over Castopod.', + ], + 'manager' => [ + 'title' => 'Leiar', + 'description' => 'Styrer innhaldet på Castopod.', + ], + 'podcaster' => [ + 'title' => 'Podkastar', + 'description' => 'Vanlege Castopod-brukarar.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Kan bruka styringspanelet for Castopod.', + 'admin.settings' => 'Kan få tilgang til innstillingane for Castopod.', + 'users.manage' => 'Kan handtera Castopod-brukarar.', + 'persons.manage' => 'Kan handtera folk.', + 'pages.manage' => 'Kan handtera sider.', + 'podcasts.view' => 'Kan sjå alle podkastane.', + 'podcasts.create' => 'Kan laga nye podkastar.', + 'podcasts.import' => 'Kan importera podkastar.', + 'fediverse.manage-blocks' => 'Kan blokkera folk og domene på allheimen frå å samhandla med Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podkasteigar', + 'description' => 'Podkasteigaren.', + ], + 'admin' => [ + 'title' => 'Administrator', + 'description' => 'Har full kontroll over podkasten #{id}.', + ], + 'editor' => [ + 'title' => 'Redaktør', + 'description' => 'Styrer innhald og publisering for podkasten #{id}.', + ], + 'author' => [ + 'title' => 'Skapar', + 'description' => 'Styrer innhald for podkasten #{id}, men kan ikkje publisera dei.', + ], + 'guest' => [ + 'title' => 'Gjest', + 'description' => 'Vanleg bidragsytar til podkasten #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Kan sjå styringspanelet og analysedata for podkasten #{id}.', + 'edit' => 'Kan redigera podkasten #{id}.', + 'delete' => 'Kan sletta podkasten #{id}.', + 'manage-import' => 'Kan synkronisera den importerte podkasten #{id}.', + 'manage-persons' => 'Kan handtera abonnement for podkasten #{id}.', + 'manage-subscriptions' => 'Kan handtera abonnement for podkasten #{id}.', + 'manage-contributors' => 'Kan handtera bidragsytarar for podkasten #{id}.', + 'manage-platforms' => 'Kan oppretta og fjerna plattformlenkjer for podkasten #{id}.', + 'manage-publications' => 'Kan publisera podkasten #{id}.', + 'manage-notifications' => 'Kan lesa og merka varsel som lesne for podkasten #{id}.', + 'interact-as' => 'Kan merka podkasten #{id} som favoritt, dela og svara på innlegg.', + 'episodes' => [ + 'view' => 'Kan sjå styringspanelet og analysedata for episodane av podkasten #{id}.', + 'create' => 'Kan laga epoisodar for podkasten #{id}.', + 'edit' => 'Kan redigera episodane av podkasten #{id}.', + 'delete' => 'Kan sletta episodar av podkasten #{id}.', + 'manage-persons' => 'Kan handtera bidragsytarar til episodar av podkasten #{id}.', + 'manage-clips' => 'Kan handtera film- og lydklypp av podkasten #{id}.', + 'manage-publications' => 'Kan publisera og avpublisera episodar og innlegg for podkasten #{id}.', + 'manage-comments' => 'Kan skriva og sletta kommentarar til episodane av podkasten #{id}.', + ], + ], + + // missing keys + 'code' => 'Den sekssifra koden din', + + 'set_password' => 'Lag eit passord', + + // Welcome email + 'welcomeSubject' => 'Du er invitert til {siteName}', + 'emailWelcomeMailBody' => 'Me har laga ein konto til deg på {domain}. Klikk på lenka under for å laga eit passord. Lenka er gyldig i {numberOfHours} timar etter eposten vart send.', +]; diff --git a/modules/Admin/Language/nn-NO/Contributor.php b/modules/Auth/Language/nn-no/Contributor.php similarity index 72% rename from modules/Admin/Language/nn-NO/Contributor.php rename to modules/Auth/Language/nn-no/Contributor.php index 0fe4cc66..941af518 100644 --- a/modules/Admin/Language/nn-NO/Contributor.php +++ b/modules/Auth/Language/nn-no/Contributor.php @@ -28,10 +28,16 @@ return [ 'submit_add' => 'Legg til bidragsytar', 'submit_edit' => 'Oppdater rolla', ], - 'roles' => [ - 'podcast_admin' => 'Podkaststyrar', + 'delete_form' => [ + 'title' => 'Fjern {contributor}', + 'disclaimer' => + 'Du er i ferd med å fjerna {contributor} som bidragsytar. Dei vil mista tilgangen til "{podcastTitle}".', + 'understand' => 'Eg forstår, og vil fjerna {contributor} frå "{podcastTitle}"', + 'submit' => 'Fjern', ], 'messages' => [ + 'editSuccess' => 'Rolle endra!', + 'editOwnerError' => "Du kan ikkje endra podkast-eigaren.", 'removeOwnerError' => "Du kan ikkje fjerna podkast-eigaren!", 'removeSuccess' => 'Du har fjerna {username} frå {podcastTitle}', diff --git a/modules/Admin/Language/nn-NO/MyAccount.php b/modules/Auth/Language/nn-no/MyAccount.php similarity index 100% rename from modules/Admin/Language/nn-NO/MyAccount.php rename to modules/Auth/Language/nn-no/MyAccount.php diff --git a/modules/Admin/Language/nn-NO/User.php b/modules/Auth/Language/nn-no/User.php similarity index 74% rename from modules/Admin/Language/nn-NO/User.php rename to modules/Auth/Language/nn-no/User.php index 0a51b30d..fedf303b 100644 --- a/modules/Admin/Language/nn-NO/User.php +++ b/modules/Auth/Language/nn-no/User.php @@ -9,8 +9,7 @@ declare(strict_types=1); */ return [ - 'edit_roles' => "Endre rollene til {username}", - 'forcePassReset' => 'Tving passordnullstilling', + 'edit_role' => "Endre rollene til {username}", 'ban' => 'Steng ute', 'unban' => 'Slepp inn att', 'delete' => 'Slett', @@ -19,7 +18,7 @@ return [ 'all_users' => 'Alle brukarane', 'list' => [ 'user' => 'Brukar', - 'roles' => 'Roller', + 'role' => 'Rolle', 'banned' => 'Utestengd?', ], 'form' => [ @@ -27,28 +26,33 @@ return [ 'username' => 'Brukarnamn', 'password' => 'Passord', 'new_password' => 'Nytt passord', + 'role' => 'Rolle', 'roles' => 'Roller', 'permissions' => 'Løyve', 'submit_create' => 'Lag brukar', 'submit_edit' => 'Lagre', 'submit_password_change' => 'Endre!', ], - 'roles' => [ - 'superadmin' => 'Superstyrar', + 'delete_form' => [ + 'title' => 'Slett {user}', + 'disclaimer' => + "Du er i ferd med å sletta {user} for alltid. Dei vil ikkje få tilgang til styringspanelet lenger.", + 'understand' => 'Eg forstår, og vil sletta {user} for alltid', + 'submit' => 'Slett', ], 'messages' => [ 'createSuccess' => 'Brukaren er oppretta! {username} vil få spørsmål om å endra passord fyrste gong hen loggar inn.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "Rollene til {username} er oppdaterte.", - 'forcePassResetSuccess' => - '{username} vil bli beden om å endra passord neste gong hen loggar inn.', 'banSuccess' => '{username} er utestengd.', 'unbanSuccess' => '{username} fekk sleppa inn att.', 'editOwnerError' => '{username} is the instance owner, you cannot edit its roles.', 'banSuperAdminError' => '{username} er superstyrar, og du stengjer ikkje ute ein superstyrar…', + 'deleteOwnerError' => + '{username} eig nettstaden. Du kan ikkje berre sletta eigaren…', 'deleteSuperAdminError' => '{username} er superstyrar, og du slettar ikkje ein superstyrar…', 'deleteSuccess' => '{username} er sletta.', diff --git a/modules/Auth/Language/oc/Auth.php b/modules/Auth/Language/oc/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/oc/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Auth/Language/oc/Contributor.php b/modules/Auth/Language/oc/Contributor.php new file mode 100644 index 00000000..c70badc0 --- /dev/null +++ b/modules/Auth/Language/oc/Contributor.php @@ -0,0 +1,47 @@ + 'Podcast contributors', + 'view' => "{username}'s contribution to {podcastTitle}", + 'add' => 'Add contributor', + 'add_contributor' => 'Add a contributor for {0}', + 'edit_role' => 'Update role for {0}', + 'edit' => 'Edit', + 'remove' => 'Remove', + 'list' => [ + 'username' => 'Username', + 'role' => 'Role', + ], + 'form' => [ + 'user' => 'User', + 'user_placeholder' => 'Select a user…', + 'role' => 'Role', + 'role_placeholder' => 'Select its role…', + 'submit_add' => 'Add contributor', + 'submit_edit' => 'Update role', + ], + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', + ], + 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", + 'removeOwnerError' => "You can't remove the podcast owner!", + 'removeSuccess' => + 'You have successfully removed {username} from {podcastTitle}', + 'alreadyAddedError' => + "The contributor you're trying to add has already been added!", + ], +]; diff --git a/modules/Auth/Language/oc/MyAccount.php b/modules/Auth/Language/oc/MyAccount.php new file mode 100644 index 00000000..6ebbb30e --- /dev/null +++ b/modules/Auth/Language/oc/MyAccount.php @@ -0,0 +1,18 @@ + 'My account info', + 'changePassword' => 'Change my password', + 'messages' => [ + 'wrongPasswordError' => "You've entered the wrong password, try again.", + 'passwordChangeSuccess' => 'Password has been successfully changed!', + ], +]; diff --git a/modules/Admin/Language/en/User.php b/modules/Auth/Language/oc/User.php similarity index 73% rename from modules/Admin/Language/en/User.php rename to modules/Auth/Language/oc/User.php index 585d6799..e7908f5b 100644 --- a/modules/Admin/Language/en/User.php +++ b/modules/Auth/Language/oc/User.php @@ -9,8 +9,7 @@ declare(strict_types=1); */ return [ - 'edit_roles' => "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', + 'edit_role' => "Edit {username}'s role", 'ban' => 'Ban', 'unban' => 'Unban', 'delete' => 'Delete', @@ -19,7 +18,7 @@ return [ 'all_users' => 'All users', 'list' => [ 'user' => 'User', - 'roles' => 'Roles', + 'role' => 'Role', 'banned' => 'Banned?', ], 'form' => [ @@ -27,28 +26,33 @@ return [ 'username' => 'Username', 'password' => 'Password', 'new_password' => 'New Password', + 'role' => 'Role', 'roles' => 'Roles', 'permissions' => 'Permissions', 'submit_create' => 'Create user', 'submit_edit' => 'Save', 'submit_password_change' => 'Change!', ], - 'roles' => [ - 'superadmin' => 'Super admin', + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', ], 'messages' => [ 'createSuccess' => 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "{username}'s roles have been successfully updated.", - 'forcePassResetSuccess' => - '{username} will be prompted with a password reset upon next visit.', 'banSuccess' => '{username} has been banned.', 'unbanSuccess' => '{username} has been unbanned.', 'editOwnerError' => '{username} is the instance owner, you cannot edit its roles.', 'banSuperAdminError' => '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', 'deleteSuperAdminError' => '{username} is a superadmin, one does not simply delete a superadmin…', 'deleteSuccess' => '{username} has been deleted.', diff --git a/modules/Auth/Language/pl/Auth.php b/modules/Auth/Language/pl/Auth.php new file mode 100644 index 00000000..bbc42f95 --- /dev/null +++ b/modules/Auth/Language/pl/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Właściciel instancji', + 'description' => 'Właściciel Castopoda.', + ], + 'superadmin' => [ + 'title' => 'Superadministrator', + 'description' => 'Ma pełną kontrolę nad Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Zarządza zawartością Castopoda.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'Zwykli użytkownicy Castopoda.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Ma dostęp do panelu administracyjnego Castopoda.', + 'admin.settings' => 'Ma dostęp do ustawień Castopoda.', + 'users.manage' => 'Może zarządzać użytkownikami Castopoda.', + 'persons.manage' => 'Może zarządzać osobami.', + 'pages.manage' => 'Może zarządzać stronami.', + 'podcasts.view' => 'Może wyświetlać wszystkie podcasty.', + 'podcasts.create' => 'Może tworzyć nowe podcasty.', + 'podcasts.import' => 'Może importować podcasty.', + 'fediverse.manage-blocks' => 'Można blokować aktorów/domeny z fediwersum przed interakcjami z Castopodem.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Właściciel Podcastu', + 'description' => 'Właściciel podcastu.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Ma pełną kontrolę nad podcastem #{id}.', + ], + 'editor' => [ + 'title' => 'Edytor', + 'description' => 'Zarządza treścią i publikacjami podcastu #{id}.', + ], + 'author' => [ + 'title' => 'Autor', + 'description' => 'Zarządza zawartością podcastu #{id}, ale nie może jej opublikować.', + ], + 'guest' => [ + 'title' => 'Gość', + 'description' => 'Główny współtwórca podcastu #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Może wyświetlań panel zarządzania oraz analitykę podcastu #{id}.', + 'edit' => 'Może edytować podcast #{id}.', + 'delete' => 'Może usunąć podcast #{id}.', + 'manage-import' => 'Może synchronizować importowany podcast #{id}.', + 'manage-persons' => 'Może zarządzać subskrypcjami podcastu #{id}.', + 'manage-subscriptions' => 'Może zarządzać subskrypcjami podcastu #{id}.', + 'manage-contributors' => 'Może zarządzać współtwórcami podcastu #{id}.', + 'manage-platforms' => 'Można ustawić/usunąć linki platformy podcastu #{id}.', + 'manage-publications' => 'Może publikować podcast #{id}.', + 'manage-notifications' => 'Może wyświetlać i oznaczać powiadomienia jako przeczytane dla podcastu #{id}.', + 'interact-as' => 'Może jako podcast #{id} dodawać do ulubionych, udostępniać lub odpowiadać na posty.', + 'episodes' => [ + 'view' => 'Może wyświetlać panel zarządzania oraz analitykę odcinków podcastu #{id}.', + 'create' => 'Może tworzyć odcinki dla podcastu #{id}.', + 'edit' => 'Może edytować odcinki podcastu #{id}.', + 'delete' => 'Można usuwać odcinki podcastu #{id}.', + 'manage-persons' => 'Może zarządzać osobami w odcinkach podcastu #{id}.', + 'manage-clips' => 'Może zarządzać klipami wideo lub zajawkami podcastu #{id}.', + 'manage-publications' => 'Może publikować/cofać publikowanie odcinków i postów podcastu #{id}.', + 'manage-comments' => 'Może tworzyć/usuwać komentarze odcinka podcastu #{id}.', + ], + ], + + // missing keys + 'code' => 'Twój 6-cyfrowy kod', + + 'set_password' => 'Ustaw swoje hasło', + + // Welcome email + 'welcomeSubject' => 'Zostałeś zaproszony do {siteName}', + 'emailWelcomeMailBody' => 'Na {domain} zostało utworzone dla Ciebie konto, kliknij poniższy link logowania, aby ustawić hasło. Link jest ważny przez {numberOfHours} godzin po wysłaniu tego e-maila.', +]; diff --git a/modules/Admin/Language/pl/Contributor.php b/modules/Auth/Language/pl/Contributor.php similarity index 67% rename from modules/Admin/Language/pl/Contributor.php rename to modules/Auth/Language/pl/Contributor.php index a00fc8b4..0a5515d1 100644 --- a/modules/Admin/Language/pl/Contributor.php +++ b/modules/Auth/Language/pl/Contributor.php @@ -28,14 +28,20 @@ return [ 'submit_add' => 'Dodaj kontrybutora', 'submit_edit' => 'Zaktualizuj rolę', ], - 'roles' => [ - 'podcast_admin' => 'Administrator podcastu', + 'delete_form' => [ + 'title' => 'Usuń {contributor}', + 'disclaimer' => + 'Zamierzasz usunąć {contributor} z wspierających. Nie będzie miał już mieć dostępu do "{podcastTitle}".', + 'understand' => 'Rozumiem, chcę usunąć {contributor} z "{podcastTitle}"', + 'submit' => 'Usuń', ], 'messages' => [ + 'editSuccess' => 'Pomyślnie zmieniono rolę!', + 'editOwnerError' => "Nie możesz edytować właściciela podcastu!", 'removeOwnerError' => "Nie możesz usunąć właściciela podcastu!", 'removeSuccess' => 'Pomyślnie usunąłeś/aś {username} z {podcastTitle}', 'alreadyAddedError' => - "Kontrybutor, którego próbujesz dodać został już dodany!", + "Kontrybutor, którego próbujesz dodać, został już dodany!", ], ]; diff --git a/modules/Admin/Language/pl/MyAccount.php b/modules/Auth/Language/pl/MyAccount.php similarity index 100% rename from modules/Admin/Language/pl/MyAccount.php rename to modules/Auth/Language/pl/MyAccount.php diff --git a/modules/Admin/Language/pl/User.php b/modules/Auth/Language/pl/User.php similarity index 74% rename from modules/Admin/Language/pl/User.php rename to modules/Auth/Language/pl/User.php index 7db87b44..69f2d96b 100644 --- a/modules/Admin/Language/pl/User.php +++ b/modules/Auth/Language/pl/User.php @@ -9,8 +9,7 @@ declare(strict_types=1); */ return [ - 'edit_roles' => "Edytuj role użytkownika {username}", - 'forcePassReset' => 'Wymuś resetowanie hasła', + 'edit_role' => "Edytuj role użytkownika {username}", 'ban' => 'Zablokuj', 'unban' => 'Odblokuj', 'delete' => 'Usuń', @@ -19,7 +18,7 @@ return [ 'all_users' => 'Wszyscy użytkownicy', 'list' => [ 'user' => 'Użytkownik', - 'roles' => 'Role', + 'role' => 'Rola', 'banned' => 'Zablokowany?', ], 'form' => [ @@ -27,28 +26,33 @@ return [ 'username' => 'Nazwa użytkownika', 'password' => 'Hasło', 'new_password' => 'Nowe hasło', + 'role' => 'Rola', 'roles' => 'Role', 'permissions' => 'Uprawnienia', 'submit_create' => 'Stwórz użytkownika', 'submit_edit' => 'Zapisz', 'submit_password_change' => 'Zmień!', ], - 'roles' => [ - 'superadmin' => 'Superadministrator', + 'delete_form' => [ + 'title' => 'Usuń użytkownika {user}', + 'disclaimer' => + "Zamierzasz usunąć {user} na stałe. Nie będą już mogli uzyskać dostępu do obszaru administratora.", + 'understand' => 'Rozumiem, chcę trwale usunąć {user}', + 'submit' => 'Usuń', ], 'messages' => [ 'createSuccess' => 'Pomyślnie utworzono użytkownika! {username} zostanie poproszony o zresetowanie hasła przy pierwszym uwierzytelnieniu.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "Role {username} zostały pomyślnie zaktualizowane.", - 'forcePassResetSuccess' => - '{username} zostanie poproszony o zresetowanie hasła przy następnej wizycie.', 'banSuccess' => '{username} został zablokowany.', 'unbanSuccess' => '{username} został odblokowany.', 'editOwnerError' => '{username} is the instance owner, you cannot edit its roles.', 'banSuperAdminError' => '{username} jest superadministratorem, nie można po prostu zablokować superadministratora…', + 'deleteOwnerError' => + '{username} jest właścicielem instancji, nie można usunąć właściciela…', 'deleteSuperAdminError' => '{username} jest superadministratorem, nie można po prostu usunąć superadministratora…', 'deleteSuccess' => '{username} został usunięty.', diff --git a/modules/Auth/Language/pt-br/Auth.php b/modules/Auth/Language/pt-br/Auth.php new file mode 100644 index 00000000..2637625b --- /dev/null +++ b/modules/Auth/Language/pt-br/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instância proprietário', + 'description' => 'O proprietário do Castopod.', + ], + 'superadmin' => [ + 'title' => 'Super administrador', + 'description' => 'Tem controle completo sobre Castopod.', + ], + 'manager' => [ + 'title' => 'Gerente', + 'description' => 'Gerencia o conteúdo de Castopod.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'Usuários gerais do Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Pode acessar a área de administração do Castopod.', + 'admin.settings' => 'Pode acessar as configurações de Castopod.', + 'users.manage' => 'Pode gerenciar usuários do Castopod.', + 'persons.manage' => 'Pode gerenciar pessoas.', + 'pages.manage' => 'Pode gerenciar páginas.', + 'podcasts.view' => 'Pode ver todos os podcasts.', + 'podcasts.create' => 'Pode criar novos podcasts.', + 'podcasts.import' => 'Pode importar podcasts.', + 'fediverse.manage-blocks' => 'Pode bloquear ator/domínios distintos de interagir com Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Dono do Podcast', + 'description' => 'O proprietário do podcast.', + ], + 'admin' => [ + 'title' => 'Administrador', + 'description' => 'Tem controle completo do podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Gerencia o conteúdo e as publicações do podcast #{id}.', + ], + 'author' => [ + 'title' => 'Autor', + 'description' => 'Gerencia o conteúdo do podcast #{id} mas não pode publicá-los.', + ], + 'guest' => [ + 'title' => 'Convidado', + 'description' => 'Contribuidor geral do podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Pode visualizar o painel de controle e análises do podcast #{id}.', + 'edit' => 'Pode editar o podcast #{id}.', + 'delete' => 'Pode deletar episódios do podcast #{id}.', + 'manage-import' => 'Pode sincronizar o podcast importado #{id}.', + 'manage-persons' => 'Pode gerenciar assinaturas do podcast #{id}.', + 'manage-subscriptions' => 'Pode gerenciar assinaturas do podcast #{id}.', + 'manage-contributors' => 'Pode gerenciar contribuidores do podcast #{id}.', + 'manage-platforms' => 'Pode definir/remover links de plataforma do podcast #{id}.', + 'manage-publications' => 'Pode publicar podcast #{id}.', + 'manage-notifications' => 'Pode ver e marcar notificações como lidas para o podcast #{id}.', + 'interact-as' => 'Pode interagir com o podcast #{id} para favorito, compartilhar ou responder às publicações.', + 'episodes' => [ + 'view' => 'Pode ver painéis e análises de episódios de podcast #{id}.', + 'create' => 'Pode criar episódios para o podcast #{id}.', + 'edit' => 'Pode editar episódios de podcast #{id}.', + 'delete' => 'Pode excluir episódios do podcast #{id}.', + 'manage-persons' => 'Pode gerenciar pessoas de episódios do podcast #{id}.', + 'manage-clips' => 'Pode gerenciar clipes de vídeo ou sons de episódios do podcast #{id}.', + 'manage-publications' => 'Pode publicar/remover a publicação de episódios e postagens de podcast #{id}.', + 'manage-comments' => 'Pode criar/remover comentários de episódio do podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Seu código de 6 dígitos', + + 'set_password' => 'Defina sua senha', + + // Welcome email + 'welcomeSubject' => 'Você foi convidado(a) para o {siteName}', + 'emailWelcomeMailBody' => 'Uma conta foi criada para você no {domain}, clique no link de login abaixo para definir sua senha. O link é válido por {numberOfHours} horas após o envio deste e-mail.', +]; diff --git a/modules/Admin/Language/pt-BR/Contributor.php b/modules/Auth/Language/pt-br/Contributor.php similarity index 72% rename from modules/Admin/Language/pt-BR/Contributor.php rename to modules/Auth/Language/pt-br/Contributor.php index 60329ef2..5dc72942 100644 --- a/modules/Admin/Language/pt-BR/Contributor.php +++ b/modules/Auth/Language/pt-br/Contributor.php @@ -28,10 +28,16 @@ return [ 'submit_add' => 'Adicionar contribuidor', 'submit_edit' => 'Atualizar cargo', ], - 'roles' => [ - 'podcast_admin' => 'Administrador do podcast', + 'delete_form' => [ + 'title' => 'Remover {contributor}', + 'disclaimer' => + 'Você está prestes a remover {contributor} dos colaboradores. Eles não poderão mais acessar "{podcastTitle}".', + 'understand' => 'Eu entendo, eu desejo remover {contributor} de "{podcastTitle}"', + 'submit' => 'Remover', ], 'messages' => [ + 'editSuccess' => 'Cargo alterado com sucesso!', + 'editOwnerError' => "Você não pode editar o dono do podcast!", 'removeOwnerError' => "Você não pode remover o dono do podcast!", 'removeSuccess' => 'Você removeu {username} com sucesso de {podcastTitle}', diff --git a/modules/Admin/Language/pt-BR/MyAccount.php b/modules/Auth/Language/pt-br/MyAccount.php similarity index 100% rename from modules/Admin/Language/pt-BR/MyAccount.php rename to modules/Auth/Language/pt-br/MyAccount.php diff --git a/modules/Admin/Language/pt-BR/User.php b/modules/Auth/Language/pt-br/User.php similarity index 74% rename from modules/Admin/Language/pt-BR/User.php rename to modules/Auth/Language/pt-br/User.php index d42b9fc6..945f8609 100644 --- a/modules/Admin/Language/pt-BR/User.php +++ b/modules/Auth/Language/pt-br/User.php @@ -9,8 +9,7 @@ declare(strict_types=1); */ return [ - 'edit_roles' => "Editar cargos de {username}", - 'forcePassReset' => 'Forçar redefinição da senha', + 'edit_role' => "Edit {username}'s role", 'ban' => 'Banir', 'unban' => 'Desbanir', 'delete' => 'Excluir', @@ -19,7 +18,7 @@ return [ 'all_users' => 'Todos os usuários', 'list' => [ 'user' => 'Usuário', - 'roles' => 'Cargos', + 'role' => 'Role', 'banned' => 'Banido?', ], 'form' => [ @@ -27,28 +26,33 @@ return [ 'username' => 'Nome de usuário', 'password' => 'Senha', 'new_password' => 'Nova Senha', + 'role' => 'Role', 'roles' => 'Cargos', 'permissions' => 'Permissões', 'submit_create' => 'Criar usuário', 'submit_edit' => 'Salvar', 'submit_password_change' => 'Alterar!', ], - 'roles' => [ - 'superadmin' => 'Super admin', + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', ], 'messages' => [ 'createSuccess' => 'Usuário criado com sucesso! {username} terá que alterar sua senha na primeira autenticação.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "Cargos de {username} foram atualizados com sucesso.", - 'forcePassResetSuccess' => - '{username} precisará alterar sua senha na próxima visita.', 'banSuccess' => '{username} foi banido.', 'unbanSuccess' => '{username} foi desbanido.', 'editOwnerError' => '{username} is the instance owner, you cannot edit its roles.', 'banSuperAdminError' => '{username} é um super admin, não bloqueamos um super admin assim…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', 'deleteSuperAdminError' => '{username} é um super admin, você não exclui um super admin assim…', 'deleteSuccess' => '{username} foi excluído.', diff --git a/modules/Auth/Language/pt/Auth.php b/modules/Auth/Language/pt/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/pt/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Auth/Language/pt/Contributor.php b/modules/Auth/Language/pt/Contributor.php new file mode 100644 index 00000000..c70badc0 --- /dev/null +++ b/modules/Auth/Language/pt/Contributor.php @@ -0,0 +1,47 @@ + 'Podcast contributors', + 'view' => "{username}'s contribution to {podcastTitle}", + 'add' => 'Add contributor', + 'add_contributor' => 'Add a contributor for {0}', + 'edit_role' => 'Update role for {0}', + 'edit' => 'Edit', + 'remove' => 'Remove', + 'list' => [ + 'username' => 'Username', + 'role' => 'Role', + ], + 'form' => [ + 'user' => 'User', + 'user_placeholder' => 'Select a user…', + 'role' => 'Role', + 'role_placeholder' => 'Select its role…', + 'submit_add' => 'Add contributor', + 'submit_edit' => 'Update role', + ], + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', + ], + 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", + 'removeOwnerError' => "You can't remove the podcast owner!", + 'removeSuccess' => + 'You have successfully removed {username} from {podcastTitle}', + 'alreadyAddedError' => + "The contributor you're trying to add has already been added!", + ], +]; diff --git a/modules/Auth/Language/pt/MyAccount.php b/modules/Auth/Language/pt/MyAccount.php new file mode 100644 index 00000000..6ebbb30e --- /dev/null +++ b/modules/Auth/Language/pt/MyAccount.php @@ -0,0 +1,18 @@ + 'My account info', + 'changePassword' => 'Change my password', + 'messages' => [ + 'wrongPasswordError' => "You've entered the wrong password, try again.", + 'passwordChangeSuccess' => 'Password has been successfully changed!', + ], +]; diff --git a/modules/Admin/Language/fa/User.php b/modules/Auth/Language/pt/User.php similarity index 73% rename from modules/Admin/Language/fa/User.php rename to modules/Auth/Language/pt/User.php index 585d6799..e7908f5b 100644 --- a/modules/Admin/Language/fa/User.php +++ b/modules/Auth/Language/pt/User.php @@ -9,8 +9,7 @@ declare(strict_types=1); */ return [ - 'edit_roles' => "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', + 'edit_role' => "Edit {username}'s role", 'ban' => 'Ban', 'unban' => 'Unban', 'delete' => 'Delete', @@ -19,7 +18,7 @@ return [ 'all_users' => 'All users', 'list' => [ 'user' => 'User', - 'roles' => 'Roles', + 'role' => 'Role', 'banned' => 'Banned?', ], 'form' => [ @@ -27,28 +26,33 @@ return [ 'username' => 'Username', 'password' => 'Password', 'new_password' => 'New Password', + 'role' => 'Role', 'roles' => 'Roles', 'permissions' => 'Permissions', 'submit_create' => 'Create user', 'submit_edit' => 'Save', 'submit_password_change' => 'Change!', ], - 'roles' => [ - 'superadmin' => 'Super admin', + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', ], 'messages' => [ 'createSuccess' => 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "{username}'s roles have been successfully updated.", - 'forcePassResetSuccess' => - '{username} will be prompted with a password reset upon next visit.', 'banSuccess' => '{username} has been banned.', 'unbanSuccess' => '{username} has been unbanned.', 'editOwnerError' => '{username} is the instance owner, you cannot edit its roles.', 'banSuperAdminError' => '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', 'deleteSuperAdminError' => '{username} is a superadmin, one does not simply delete a superadmin…', 'deleteSuccess' => '{username} has been deleted.', diff --git a/modules/Auth/Language/ro/Auth.php b/modules/Auth/Language/ro/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/ro/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Auth/Language/ro/Contributor.php b/modules/Auth/Language/ro/Contributor.php new file mode 100644 index 00000000..c70badc0 --- /dev/null +++ b/modules/Auth/Language/ro/Contributor.php @@ -0,0 +1,47 @@ + 'Podcast contributors', + 'view' => "{username}'s contribution to {podcastTitle}", + 'add' => 'Add contributor', + 'add_contributor' => 'Add a contributor for {0}', + 'edit_role' => 'Update role for {0}', + 'edit' => 'Edit', + 'remove' => 'Remove', + 'list' => [ + 'username' => 'Username', + 'role' => 'Role', + ], + 'form' => [ + 'user' => 'User', + 'user_placeholder' => 'Select a user…', + 'role' => 'Role', + 'role_placeholder' => 'Select its role…', + 'submit_add' => 'Add contributor', + 'submit_edit' => 'Update role', + ], + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', + ], + 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", + 'removeOwnerError' => "You can't remove the podcast owner!", + 'removeSuccess' => + 'You have successfully removed {username} from {podcastTitle}', + 'alreadyAddedError' => + "The contributor you're trying to add has already been added!", + ], +]; diff --git a/modules/Auth/Language/ro/MyAccount.php b/modules/Auth/Language/ro/MyAccount.php new file mode 100644 index 00000000..6ebbb30e --- /dev/null +++ b/modules/Auth/Language/ro/MyAccount.php @@ -0,0 +1,18 @@ + 'My account info', + 'changePassword' => 'Change my password', + 'messages' => [ + 'wrongPasswordError' => "You've entered the wrong password, try again.", + 'passwordChangeSuccess' => 'Password has been successfully changed!', + ], +]; diff --git a/modules/Auth/Language/ro/User.php b/modules/Auth/Language/ro/User.php new file mode 100644 index 00000000..32ec560c --- /dev/null +++ b/modules/Auth/Language/ro/User.php @@ -0,0 +1,60 @@ + "Edit {username}'s role", + 'ban' => 'Ban', + 'unban' => 'Unban', + 'delete' => 'Delete', + 'create' => 'New user', + 'view' => "{username}'s info", + 'all_users' => 'All users', + 'list' => [ + 'user' => 'User', + 'role' => 'Role', + 'banned' => 'Banned?', + ], + 'form' => [ + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + 'new_password' => 'New Password', + 'role' => 'Role', + 'roles' => 'Roles', + 'permissions' => 'Permissions', + 'submit_create' => 'Create user', + 'submit_edit' => 'Save', + 'submit_password_change' => 'Change!', + ], + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', + ], + 'messages' => [ + 'createSuccess' => + 'User created successfully! A welcome email was sent to {username} with a login link, they will be prompted with a password reset upon first authentication.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + 'banSuccess' => '{username} has been banned.', + 'unbanSuccess' => '{username} has been unbanned.', + 'editOwnerError' => + '{username} is the instance owner, one does not simply touch the owner…', + 'banSuperAdminError' => + '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', + 'deleteSuperAdminError' => + '{username} is a superadmin, one does not simply delete a superadmin…', + 'deleteSuccess' => '{username} has been deleted.', + ], +]; diff --git a/modules/Auth/Language/ru/Auth.php b/modules/Auth/Language/ru/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/ru/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Auth/Language/ru/Contributor.php b/modules/Auth/Language/ru/Contributor.php new file mode 100644 index 00000000..c70badc0 --- /dev/null +++ b/modules/Auth/Language/ru/Contributor.php @@ -0,0 +1,47 @@ + 'Podcast contributors', + 'view' => "{username}'s contribution to {podcastTitle}", + 'add' => 'Add contributor', + 'add_contributor' => 'Add a contributor for {0}', + 'edit_role' => 'Update role for {0}', + 'edit' => 'Edit', + 'remove' => 'Remove', + 'list' => [ + 'username' => 'Username', + 'role' => 'Role', + ], + 'form' => [ + 'user' => 'User', + 'user_placeholder' => 'Select a user…', + 'role' => 'Role', + 'role_placeholder' => 'Select its role…', + 'submit_add' => 'Add contributor', + 'submit_edit' => 'Update role', + ], + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', + ], + 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", + 'removeOwnerError' => "You can't remove the podcast owner!", + 'removeSuccess' => + 'You have successfully removed {username} from {podcastTitle}', + 'alreadyAddedError' => + "The contributor you're trying to add has already been added!", + ], +]; diff --git a/modules/Auth/Language/ru/MyAccount.php b/modules/Auth/Language/ru/MyAccount.php new file mode 100644 index 00000000..6ebbb30e --- /dev/null +++ b/modules/Auth/Language/ru/MyAccount.php @@ -0,0 +1,18 @@ + 'My account info', + 'changePassword' => 'Change my password', + 'messages' => [ + 'wrongPasswordError' => "You've entered the wrong password, try again.", + 'passwordChangeSuccess' => 'Password has been successfully changed!', + ], +]; diff --git a/modules/Auth/Language/ru/User.php b/modules/Auth/Language/ru/User.php new file mode 100644 index 00000000..e7908f5b --- /dev/null +++ b/modules/Auth/Language/ru/User.php @@ -0,0 +1,60 @@ + "Edit {username}'s role", + 'ban' => 'Ban', + 'unban' => 'Unban', + 'delete' => 'Delete', + 'create' => 'New user', + 'view' => "{username}'s info", + 'all_users' => 'All users', + 'list' => [ + 'user' => 'User', + 'role' => 'Role', + 'banned' => 'Banned?', + ], + 'form' => [ + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + 'new_password' => 'New Password', + 'role' => 'Role', + 'roles' => 'Roles', + 'permissions' => 'Permissions', + 'submit_create' => 'Create user', + 'submit_edit' => 'Save', + 'submit_password_change' => 'Change!', + ], + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', + ], + 'messages' => [ + 'createSuccess' => + 'User created successfully! {username} will be prompted with a password reset upon first authentication.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + 'banSuccess' => '{username} has been banned.', + 'unbanSuccess' => '{username} has been unbanned.', + 'editOwnerError' => + '{username} is the instance owner, you cannot edit its roles.', + 'banSuperAdminError' => + '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', + 'deleteSuperAdminError' => + '{username} is a superadmin, one does not simply delete a superadmin…', + 'deleteSuccess' => '{username} has been deleted.', + ], +]; diff --git a/modules/Auth/Language/sk/Auth.php b/modules/Auth/Language/sk/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/sk/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Admin/Language/sk/Contributor.php b/modules/Auth/Language/sk/Contributor.php similarity index 73% rename from modules/Admin/Language/sk/Contributor.php rename to modules/Auth/Language/sk/Contributor.php index 3f0991fa..2f251c38 100644 --- a/modules/Admin/Language/sk/Contributor.php +++ b/modules/Auth/Language/sk/Contributor.php @@ -28,10 +28,16 @@ return [ 'submit_add' => 'Pridať prispievateľa', 'submit_edit' => 'Aktualizovať rolu', ], - 'roles' => [ - 'podcast_admin' => 'Správca podcastu', + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', ], 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", 'removeOwnerError' => "Nemôžete odstrániť vlastníka podcastu!", 'removeSuccess' => 'Úspešne ste odstránili používateľa {username} z podcastu {podcastTitle}', diff --git a/modules/Admin/Language/sk/MyAccount.php b/modules/Auth/Language/sk/MyAccount.php similarity index 100% rename from modules/Admin/Language/sk/MyAccount.php rename to modules/Auth/Language/sk/MyAccount.php diff --git a/modules/Auth/Language/sk/User.php b/modules/Auth/Language/sk/User.php new file mode 100644 index 00000000..e7908f5b --- /dev/null +++ b/modules/Auth/Language/sk/User.php @@ -0,0 +1,60 @@ + "Edit {username}'s role", + 'ban' => 'Ban', + 'unban' => 'Unban', + 'delete' => 'Delete', + 'create' => 'New user', + 'view' => "{username}'s info", + 'all_users' => 'All users', + 'list' => [ + 'user' => 'User', + 'role' => 'Role', + 'banned' => 'Banned?', + ], + 'form' => [ + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + 'new_password' => 'New Password', + 'role' => 'Role', + 'roles' => 'Roles', + 'permissions' => 'Permissions', + 'submit_create' => 'Create user', + 'submit_edit' => 'Save', + 'submit_password_change' => 'Change!', + ], + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', + ], + 'messages' => [ + 'createSuccess' => + 'User created successfully! {username} will be prompted with a password reset upon first authentication.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + 'banSuccess' => '{username} has been banned.', + 'unbanSuccess' => '{username} has been unbanned.', + 'editOwnerError' => + '{username} is the instance owner, you cannot edit its roles.', + 'banSuperAdminError' => + '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', + 'deleteSuperAdminError' => + '{username} is a superadmin, one does not simply delete a superadmin…', + 'deleteSuccess' => '{username} has been deleted.', + ], +]; diff --git a/modules/Auth/Language/sr-latn/Auth.php b/modules/Auth/Language/sr-latn/Auth.php new file mode 100644 index 00000000..82d4174f --- /dev/null +++ b/modules/Auth/Language/sr-latn/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Vlasnik instance', + 'description' => 'Vlasnik Castopoda.', + ], + 'superadmin' => [ + 'title' => 'Super administrator', + 'description' => 'Ima kompletnu kontrolu nad Castopod-om.', + ], + 'manager' => [ + 'title' => 'Menadžer', + 'description' => 'Upravlja sadržajem na Castopod-u.', + ], + 'podcaster' => [ + 'title' => 'Podkaster', + 'description' => 'Opšti korisnici Castopod-a.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Može pristupiti administratorskom delu Castopod-a.', + 'admin.settings' => 'Može pristupiti podešavanjima Castopod-a.', + 'users.manage' => 'Može upravljati korisnicima Castopod-a.', + 'persons.manage' => 'Može upravljati osobama.', + 'pages.manage' => 'Može upravljati stranicama.', + 'podcasts.view' => 'Može videti sve podkaste.', + 'podcasts.create' => 'Može napraviti nove podkaste.', + 'podcasts.import' => 'Može uvesti nove podkaste.', + 'fediverse.manage-blocks' => 'Može blokirati interakciju Castopoda i fedivers naloga/domena.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Vlasnik podkasta', + 'description' => 'Vlasnik podkasta.', + ], + 'admin' => [ + 'title' => 'Administrator', + 'description' => 'Ima kompletnu kontrolu nad podkastom #{id}.', + ], + 'editor' => [ + 'title' => 'Urednik', + 'description' => 'Upravlja sadržajem i objavama podkasta #{id}.', + ], + 'author' => [ + 'title' => 'Autor', + 'description' => 'Upravlja sadržajem podkasta #{id} ali ne može da ga objavi.', + ], + 'guest' => [ + 'title' => 'Gost', + 'description' => 'Saradnik na podkastu #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Može videti upravljačku tablu i analitiku podkasta #{id}.', + 'edit' => 'Može uređivati podkast #{id}.', + 'delete' => 'Može obrisati podkast #{id}.', + 'manage-import' => 'Može sinhronizovati uvezen podkast #{id}.', + 'manage-persons' => 'Može upravljati pretplatama na podkast #{id}.', + 'manage-subscriptions' => 'Može upravljati pretplatama na podkast #{id}.', + 'manage-contributors' => 'Može upravljati saradnicima na podkastu #{id}.', + 'manage-platforms' => 'Može ubaciti/izbaciti veze ka platformama podkasta #{id}.', + 'manage-publications' => 'Može objaviti podkast #{id}.', + 'manage-notifications' => 'Može videti obaveštenja i označiti ih kao pročitana za podkast #{id}.', + 'interact-as' => 'Može da komunicira kao podkast #{id} i deli, odgovara na i stavlja u omiljene postove.', + 'episodes' => [ + 'view' => 'Može videti upravljačku tablu i analitiku epizoda podkasta #{id}.', + 'create' => 'Može napraviti epizode podkasta #{id}.', + 'edit' => 'Može uređivati epizode podkasta #{id}.', + 'delete' => 'Može obrisati epizode podkasta #{id}.', + 'manage-persons' => 'Može upravljati osobama na epizodama podkasta #{id}.', + 'manage-clips' => 'Može upravljati video klipovima i zvučnim isečcima podkasta #{id}.', + 'manage-publications' => 'Može da objavi/poništi objavljivanje epizoda i postova podkasta #{id}.', + 'manage-comments' => 'Može dodati/obrisati komentar na epizodi podkasta #{id}.', + ], + ], + + // missing keys + 'code' => 'Vaša šestocifrena šifra', + + 'set_password' => 'Podesi lozinku', + + // Welcome email + 'welcomeSubject' => 'Pozvani ste na {siteName}', + 'emailWelcomeMailBody' => 'Za vas je napravljen nalog na {domain}, kliknite na link za prijavu ispod da biste postavili lozinku. Veza je važeća {numberOfHours} sati nakon slanja ove e-pošte.', +]; diff --git a/modules/Auth/Language/sr-latn/Contributor.php b/modules/Auth/Language/sr-latn/Contributor.php new file mode 100644 index 00000000..39bcbdb9 --- /dev/null +++ b/modules/Auth/Language/sr-latn/Contributor.php @@ -0,0 +1,47 @@ + 'Saradnici podkasta', + 'view' => "{username} doprinos {podcastTitle}", + 'add' => 'Dodaj saradnika', + 'add_contributor' => 'Dodaj saradnike za {0}', + 'edit_role' => 'Uredi ulogu za {0}', + 'edit' => 'Izmeni', + 'remove' => 'Ukloni', + 'list' => [ + 'username' => 'Korisničko ime', + 'role' => 'Uloga', + ], + 'form' => [ + 'user' => 'Korisnik', + 'user_placeholder' => 'Izaberi korisnika…', + 'role' => 'Uloga', + 'role_placeholder' => 'Dodaj ulogu…', + 'submit_add' => 'Dodaj saradnika', + 'submit_edit' => 'Ažuriraj ulogu', + ], + 'delete_form' => [ + 'title' => 'Ukloni {contributor}', + 'disclaimer' => + 'Obrisaćete {contributor} iz saradnika. Oni neće moći više da pristupe "{podcastTitle}".', + 'understand' => 'Razumem, želim da uklonim {contributor} iz "{podcastTitle}"', + 'submit' => 'Ukloni', + ], + 'messages' => [ + 'editSuccess' => 'Uloga uspešno promenjena!', + 'editOwnerError' => "Ne možete urediti vlasnika podkasta!", + 'removeOwnerError' => "Ne možete ukloniti vlasnika podkasta!", + 'removeSuccess' => + 'Uspešno ste uklonili {username} iz {podcastTitle}', + 'alreadyAddedError' => + "Saradnik kojeg pokušavate dodati je već dodat!", + ], +]; diff --git a/modules/Auth/Language/sr-latn/MyAccount.php b/modules/Auth/Language/sr-latn/MyAccount.php new file mode 100644 index 00000000..5fa13d74 --- /dev/null +++ b/modules/Auth/Language/sr-latn/MyAccount.php @@ -0,0 +1,18 @@ + 'Infromacije o mom nalogu', + 'changePassword' => 'Promeni moju lozinku', + 'messages' => [ + 'wrongPasswordError' => "Uneli ste pogrešnu lozinku, probajte ponovo.", + 'passwordChangeSuccess' => 'Lozinka je uspešno promenjena!', + ], +]; diff --git a/modules/Auth/Language/sr-latn/User.php b/modules/Auth/Language/sr-latn/User.php new file mode 100644 index 00000000..5e128ed8 --- /dev/null +++ b/modules/Auth/Language/sr-latn/User.php @@ -0,0 +1,60 @@ + "Uredi {username} uloge", + 'ban' => 'Zabrani', + 'unban' => 'Ukini zabranu', + 'delete' => 'Obriši', + 'create' => 'Novi korisnik', + 'view' => "Informacije o korisniku {username}", + 'all_users' => 'Svi korisnici', + 'list' => [ + 'user' => 'Korisnik', + 'role' => 'Uloga', + 'banned' => 'Zabranjen?', + ], + 'form' => [ + 'email' => 'E-pošta', + 'username' => 'Korisničko ime', + 'password' => 'Lozinka', + 'new_password' => 'Nova lozinka', + 'role' => 'Uloga', + 'roles' => 'Uloge', + 'permissions' => 'Dozvole', + 'submit_create' => 'Kreiraj korisnika', + 'submit_edit' => 'Sačuvaj', + 'submit_password_change' => 'Promeni!', + ], + 'delete_form' => [ + 'title' => 'Ukloni korisnika {user}', + 'disclaimer' => + "Spremate se da trajno uklonite korisnika {user}. Korisnik neće moći više da pristupi administratorskoj zoni.", + 'understand' => 'Shvatam, želim da trajno uklonim korisnika {user}', + 'submit' => 'Obriši', + ], + 'messages' => [ + 'createSuccess' => + 'Korisnik je uspešno kreiran! Poruka dobrodošlice je poslata E-poštom korisniku {username}. Ona sadrži vezu za prijavu a od njih će biti zatraženo resetovanje lozinke nakon prve autentifikacije.', + 'roleEditSuccess' => + "Uloge korisnika {username} su uspešno ažurirane.", + 'banSuccess' => 'Korisnik {username} je zabranjen.', + 'unbanSuccess' => 'Korisniku {username} je skinuta zabrana.', + 'editOwnerError' => + 'Korisnik {username} je vlasnik instance, prosto ne možete dirati vlasnika…', + 'banSuperAdminError' => + 'Korisnik {username} je super administrator, prosto ne možete zabraniti super administratora…', + 'deleteOwnerError' => + 'Korisnik {username} je vlasnik instance, prosto ne možete obrisati vlasnika…', + 'deleteSuperAdminError' => + 'Korisnik {username} je super administrator, prosto ne možete obrisati super administratora…', + 'deleteSuccess' => 'Korisnik {username} je obrisan.', + ], +]; diff --git a/modules/Auth/Language/sv/Auth.php b/modules/Auth/Language/sv/Auth.php new file mode 100644 index 00000000..386853fe --- /dev/null +++ b/modules/Auth/Language/sv/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instans Ägare', + 'description' => 'Castopod ägaren.', + ], + 'superadmin' => [ + 'title' => 'Super administratör', + 'description' => 'Har fullständig kontroll över Castopod.', + ], + 'manager' => [ + 'title' => 'Hanterare', + 'description' => 'Hanterar Castopods innehåll.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'Generella användare av Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Kan komma åt Castopod admin-området.', + 'admin.settings' => 'Kan komma åt Castopod-inställningarna.', + 'users.manage' => 'Kan hantera Castopod-användare.', + 'persons.manage' => 'Kan hantera personer.', + 'pages.manage' => 'Kan hantera sidor.', + 'podcasts.view' => 'Kan se alla podcasts.', + 'podcasts.create' => 'Kan skapa nya podcasts.', + 'podcasts.import' => 'Kan importera podcasts.', + 'fediverse.manage-blocks' => 'Kan blockera fediverse skådespelare/domäner från att interagera med Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast ägare', + 'description' => 'Podcast ägaren.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Har fullständig kontroll över podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Redigerare', + 'description' => 'Hanterar innehåll och publikationer i podcast #{id}.', + ], + 'author' => [ + 'title' => 'Författare', + 'description' => 'Hanterar innehåll i podcast #{id} men kan inte publicera dem.', + ], + 'guest' => [ + 'title' => 'Gäst', + 'description' => 'Generell bidragsgivare till podcasten #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Kan visa instrumentpanelen och analysen av podcast #{id}.', + 'edit' => 'Kan redigera podcast #{id}.', + 'delete' => 'Kan ta bort podcast #{id}.', + 'manage-import' => 'Kan synkronisera importerad podcast #{id}.', + 'manage-persons' => 'Kan hantera prenumerationer på podcast #{id}.', + 'manage-subscriptions' => 'Kan hantera prenumerationer på podcast #{id}.', + 'manage-contributors' => 'Kan hantera bidragsgivare för podcast #{id}.', + 'manage-platforms' => 'Kan sätta/ta bort plattformslänkar för podcast #{id}.', + 'manage-publications' => 'Kan publicera podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Kan interagera som podcasten #{id} för att favorita, dela eller svara på inlägg.', + 'episodes' => [ + 'view' => 'Kan visa instrumentpaneler och analyser av podcast #{id}s avsnitt.', + 'create' => 'Kan skapa avsnitt för podcast #{id}.', + 'edit' => 'Kan redigera avsnitt av podcast #{id}.', + 'delete' => 'Kan ta bort avsnitt av podcast #{id}.', + 'manage-persons' => 'Kan hantera avsnittpersoner i podcast #{id}.', + 'manage-clips' => 'Kan hantera videoklipp eller ljudklipp från podcasten #{id}.', + 'manage-publications' => 'Kan publicera/avpublicera avsnitt och inlägg i podcast #{id}.', + 'manage-comments' => 'Kan skapa/ta bort avsnitt kommentarer från podcasten #{id}.', + ], + ], + + // missing keys + 'code' => 'Din 6-siffriga kod', + + 'set_password' => 'Välj ett lösenord', + + // Welcome email + 'welcomeSubject' => 'Du har blivit inbjuden till {siteName}', + 'emailWelcomeMailBody' => 'Ett konto skapades för dig på {domain}, klicka på inloggningslänken nedan för att ange ditt lösenord. Länken är giltig i {numberOfHours} timmar efter att detta e-postmeddelande skickats.', +]; diff --git a/modules/Auth/Language/sv/Contributor.php b/modules/Auth/Language/sv/Contributor.php new file mode 100644 index 00000000..572b91ad --- /dev/null +++ b/modules/Auth/Language/sv/Contributor.php @@ -0,0 +1,47 @@ + 'Podcast contributors', + 'view' => "{username}'s contribution to {podcastTitle}", + 'add' => 'Add contributor', + 'add_contributor' => 'Add a contributor for {0}', + 'edit_role' => 'Update role for {0}', + 'edit' => 'Redigera', + 'remove' => 'Remove', + 'list' => [ + 'username' => 'Användarnamn', + 'role' => 'Roll', + ], + 'form' => [ + 'user' => 'User', + 'user_placeholder' => 'Select a user…', + 'role' => 'Roll', + 'role_placeholder' => 'Select its role…', + 'submit_add' => 'Add contributor', + 'submit_edit' => 'Uppdatera roll', + ], + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', + ], + 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", + 'removeOwnerError' => "You can't remove the podcast owner!", + 'removeSuccess' => + 'You have successfully removed {username} from {podcastTitle}', + 'alreadyAddedError' => + "The contributor you're trying to add has already been added!", + ], +]; diff --git a/modules/Auth/Language/sv/MyAccount.php b/modules/Auth/Language/sv/MyAccount.php new file mode 100644 index 00000000..78a66902 --- /dev/null +++ b/modules/Auth/Language/sv/MyAccount.php @@ -0,0 +1,18 @@ + 'My account info', + 'changePassword' => 'Ändra mitt lösenord', + 'messages' => [ + 'wrongPasswordError' => "You've entered the wrong password, try again.", + 'passwordChangeSuccess' => 'Password has been successfully changed!', + ], +]; diff --git a/modules/Auth/Language/sv/User.php b/modules/Auth/Language/sv/User.php new file mode 100644 index 00000000..d5a57d48 --- /dev/null +++ b/modules/Auth/Language/sv/User.php @@ -0,0 +1,60 @@ + "Edit {username}'s role", + 'ban' => 'Ban', + 'unban' => 'Unban', + 'delete' => 'Radera', + 'create' => 'New user', + 'view' => "{username}'s info", + 'all_users' => 'All users', + 'list' => [ + 'user' => 'User', + 'role' => 'Role', + 'banned' => 'Banned?', + ], + 'form' => [ + 'email' => 'Email', + 'username' => 'Användarnamn', + 'password' => 'Lösenord', + 'new_password' => 'Nytt lösenord', + 'role' => 'Role', + 'roles' => 'Roles', + 'permissions' => 'Permissions', + 'submit_create' => 'Create user', + 'submit_edit' => 'Save', + 'submit_password_change' => 'Change!', + ], + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', + ], + 'messages' => [ + 'createSuccess' => + 'User created successfully! {username} will be prompted with a password reset upon first authentication.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + 'banSuccess' => '{username} has been banned.', + 'unbanSuccess' => '{username} has been unbanned.', + 'editOwnerError' => + '{username} is the instance owner, you cannot edit its roles.', + 'banSuperAdminError' => + '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', + 'deleteSuperAdminError' => + '{username} is a superadmin, one does not simply delete a superadmin…', + 'deleteSuccess' => '{username} has been deleted.', + ], +]; diff --git a/modules/Auth/Language/uk/Auth.php b/modules/Auth/Language/uk/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/uk/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Auth/Language/uk/Contributor.php b/modules/Auth/Language/uk/Contributor.php new file mode 100644 index 00000000..c70badc0 --- /dev/null +++ b/modules/Auth/Language/uk/Contributor.php @@ -0,0 +1,47 @@ + 'Podcast contributors', + 'view' => "{username}'s contribution to {podcastTitle}", + 'add' => 'Add contributor', + 'add_contributor' => 'Add a contributor for {0}', + 'edit_role' => 'Update role for {0}', + 'edit' => 'Edit', + 'remove' => 'Remove', + 'list' => [ + 'username' => 'Username', + 'role' => 'Role', + ], + 'form' => [ + 'user' => 'User', + 'user_placeholder' => 'Select a user…', + 'role' => 'Role', + 'role_placeholder' => 'Select its role…', + 'submit_add' => 'Add contributor', + 'submit_edit' => 'Update role', + ], + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', + ], + 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", + 'removeOwnerError' => "You can't remove the podcast owner!", + 'removeSuccess' => + 'You have successfully removed {username} from {podcastTitle}', + 'alreadyAddedError' => + "The contributor you're trying to add has already been added!", + ], +]; diff --git a/modules/Auth/Language/uk/MyAccount.php b/modules/Auth/Language/uk/MyAccount.php new file mode 100644 index 00000000..0b511d51 --- /dev/null +++ b/modules/Auth/Language/uk/MyAccount.php @@ -0,0 +1,18 @@ + 'Інформація про аккаунт', + 'changePassword' => 'Змінити мій пароль', + 'messages' => [ + 'wrongPasswordError' => "Ви ввели неправильний пароль, спробуйте ще раз.", + 'passwordChangeSuccess' => 'Ваш пароль успішно змінено.', + ], +]; diff --git a/modules/Auth/Language/uk/User.php b/modules/Auth/Language/uk/User.php new file mode 100644 index 00000000..a9383f65 --- /dev/null +++ b/modules/Auth/Language/uk/User.php @@ -0,0 +1,60 @@ + "Змінити роль «%{name}»", + 'ban' => 'Забанити', + 'unban' => 'Розбанити', + 'delete' => 'Видалити', + 'create' => 'Новий користувач', + 'view' => "{username}інформація", + 'all_users' => 'Усі користувачі', + 'list' => [ + 'user' => 'Користувач', + 'role' => 'Роль', + 'banned' => 'Забанені?', + ], + 'form' => [ + 'email' => 'Пошта', + 'username' => 'Ім\'я користувача', + 'password' => 'Пароль', + 'new_password' => 'Новий пароль', + 'role' => 'Роль', + 'roles' => 'Ролі', + 'permissions' => 'Дозволи', + 'submit_create' => 'Створити користувача', + 'submit_edit' => 'Зберегти', + 'submit_password_change' => 'Змінити', + ], + 'delete_form' => [ + 'title' => 'Видалити користувача?', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', + ], + 'messages' => [ + 'createSuccess' => + 'User created successfully! A welcome email was sent to {username} with a login link, they will be prompted with a password reset upon first authentication.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + 'banSuccess' => '{username} has been banned.', + 'unbanSuccess' => '{username} has been unbanned.', + 'editOwnerError' => + '{username} is the instance owner, one does not simply touch the owner…', + 'banSuperAdminError' => + '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', + 'deleteSuperAdminError' => + '{username} is a superadmin, one does not simply delete a superadmin…', + 'deleteSuccess' => '{username} has been deleted.', + ], +]; diff --git a/modules/Auth/Language/zh-hans/Auth.php b/modules/Auth/Language/zh-hans/Auth.php new file mode 100644 index 00000000..b3d371e5 --- /dev/null +++ b/modules/Auth/Language/zh-hans/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => '实例所有者', + 'description' => 'Castopod 所有者', + ], + 'superadmin' => [ + 'title' => '超级管理员', + 'description' => '拥有对 Castopod 的完全控制。', + ], + 'manager' => [ + 'title' => '管理', + 'description' => '管理 Castopod 的内容。', + ], + 'podcaster' => [ + 'title' => '播客', + 'description' => 'Castopod 的普通用户。', + ], + ], + 'instance_permissions' => [ + 'admin.access' => '可以访问 Castopod 管理区域。', + 'admin.settings' => '可以访问 Castopod 设置。', + 'users.manage' => '可以管理 Castopod 用户。', + 'persons.manage' => '可以管理人员。', + 'pages.manage' => '可以管理页面。', + 'podcasts.view' => '可以查看所有播客。', + 'podcasts.create' => '可以创建新播客。', + 'podcasts.import' => '可以导入播客。', + 'fediverse.manage-blocks' => '可以阻止联邦宇宙参与者/域与 Castopod 交互。', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => '播客封面', + 'description' => '播客所有者。', + ], + 'admin' => [ + 'title' => '管理员', + 'description' => '完全控制播客 #{id}。', + ], + 'editor' => [ + 'title' => '编辑', + 'description' => '管理播客 #{id} 的内容和出版物。', + ], + 'author' => [ + 'title' => '作者', + 'description' => '管理播客 #{id} 的内容,但不能发布。', + ], + 'guest' => [ + 'title' => '访客', + 'description' => '播客 #{id} 的普通贡献者。', + ], + ], + 'podcast_permissions' => [ + 'view' => '可以查看播客 #{id} 的仪表板和分析。', + 'edit' => '可以编辑播客 #{id}。', + 'delete' => '可以删除播客 #{id}。', + 'manage-import' => '可以同步导入的播客 #{id}。', + 'manage-persons' => '可以管理播客 #{id} 的订阅。', + 'manage-subscriptions' => '可以管理播客 #{id} 的订阅。', + 'manage-contributors' => '可以管理播客 #{id} 的贡献者。', + 'manage-platforms' => '可以设置/删除播客 #{id} 的平台链接。', + 'manage-publications' => '可以发布播客 #{id}。', + 'manage-notifications' => '可以查看播客 #{id} 的通知并将其标记为已读。', + 'interact-as' => '可以在播客 #{id} 进行互动,以收藏、分享或回复帖子。', + 'episodes' => [ + 'view' => '可以查看播客 #{id} 的仪表板和分析。', + 'create' => '可以为播客 #{id} 创建剧集。', + 'edit' => '可以编辑播客 #{id} 的剧集。', + 'delete' => '可以删除播客 #{id} 的剧集。', + 'manage-persons' => '可以管理播客 #{id} 的剧集人。', + 'manage-clips' => '可以管理播客 #{id} 的视频剪辑或声音片段。', + 'manage-publications' => '可以发布/取消发布播客 #{id} 的剧集和帖子。', + 'manage-comments' => '可以创建/删除播客 #{id} 的剧集评论。', + ], + ], + + // missing keys + 'code' => '你的6位验证码', + + 'set_password' => '设置你的密码', + + // Welcome email + 'welcomeSubject' => '你已受邀加入 {siteName}', + 'emailWelcomeMailBody' => '在 {domain} 上为你创建了一个帐户,单击下面的登录链接设置您的密码。 该链接在发送此电子邮件后的 {numberOfHours} 小时内有效。', +]; diff --git a/modules/Admin/Language/zh-Hans/Contributor.php b/modules/Auth/Language/zh-hans/Contributor.php similarity index 71% rename from modules/Admin/Language/zh-Hans/Contributor.php rename to modules/Auth/Language/zh-hans/Contributor.php index d90ef87d..0d28c5ad 100644 --- a/modules/Admin/Language/zh-Hans/Contributor.php +++ b/modules/Auth/Language/zh-hans/Contributor.php @@ -28,10 +28,16 @@ return [ 'submit_add' => '添加贡献者', 'submit_edit' => '更新角色', ], - 'roles' => [ - 'podcast_admin' => '播客管理员', + 'delete_form' => [ + 'title' => '移除 {contributor}', + 'disclaimer' => + '你将要从贡献者中删除 {contributor},他们将无法再访问“{podcastTitle}”。', + 'understand' => '我明白,我想从“{podcastTitle}”中删除 {contributor}', + 'submit' => '移除', ], 'messages' => [ + 'editSuccess' => '已成功更改角色!', + 'editOwnerError' => "你无法编辑播客所有者!", 'removeOwnerError' => "你无法删除播客所有者!", 'removeSuccess' => '你从 {username} 移除 {podcastTitle}', diff --git a/modules/Admin/Language/zh-Hans/MyAccount.php b/modules/Auth/Language/zh-hans/MyAccount.php similarity index 100% rename from modules/Admin/Language/zh-Hans/MyAccount.php rename to modules/Auth/Language/zh-hans/MyAccount.php diff --git a/modules/Admin/Language/zh-Hans/User.php b/modules/Auth/Language/zh-hans/User.php similarity index 74% rename from modules/Admin/Language/zh-Hans/User.php rename to modules/Auth/Language/zh-hans/User.php index cb3e0ed6..5440c60a 100644 --- a/modules/Admin/Language/zh-Hans/User.php +++ b/modules/Auth/Language/zh-hans/User.php @@ -9,8 +9,7 @@ declare(strict_types=1); */ return [ - 'edit_roles' => "编辑 {username} 的角色", - 'forcePassReset' => '强制重置', + 'edit_role' => "编辑 {username} 的角色", 'ban' => '封禁', 'unban' => '取消封禁', 'delete' => '删除', @@ -19,7 +18,7 @@ return [ 'all_users' => '所有用户', 'list' => [ 'user' => '用户', - 'roles' => '角色', + 'role' => '角色', 'banned' => '已封禁?', ], 'form' => [ @@ -27,28 +26,33 @@ return [ 'username' => '用户名', 'password' => '密码', 'new_password' => '新密码', + 'role' => '角色', 'roles' => '角色', 'permissions' => '权限', 'submit_create' => '创建用户', 'submit_edit' => '保存', 'submit_password_change' => '修改!', ], - 'roles' => [ - 'superadmin' => '超级管理员', + 'delete_form' => [ + 'title' => '删除 {user} ?', + 'disclaimer' => + "你将永久删除 {user},他们将无法再访问管理区域。", + 'understand' => '我明白,我想永久删除 {user}', + 'submit' => '删除', ], 'messages' => [ 'createSuccess' => '用户创建成功!{username} 将在首次验证时提醒重置密码。', - 'rolesEditSuccess' => + 'roleEditSuccess' => "{username} 的角色已更新。", - 'forcePassResetSuccess' => - '下次访问时 {username} 将被提醒重置密码。', 'banSuccess' => '{username} 已被封禁。', 'unbanSuccess' => '{username} 已解除封禁。', 'editOwnerError' => '{username} 是实例的所有者,你不能编辑他的角色。', 'banSuperAdminError' => '{username} 是超级管理员,不能禁止超级管理员…', + 'deleteOwnerError' => + '{username} 是实例的所有者,不能简单地删除所有者…', 'deleteSuperAdminError' => '{username} 是超级管理员,不能封禁超级管理员…', 'deleteSuccess' => '{username} 已被删除。', diff --git a/modules/Auth/Language/zh-hant/Auth.php b/modules/Auth/Language/zh-hant/Auth.php new file mode 100644 index 00000000..b366563a --- /dev/null +++ b/modules/Auth/Language/zh-hant/Auth.php @@ -0,0 +1,95 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + 'admin.access' => 'Can access the Castopod admin area.', + 'admin.settings' => 'Can access the Castopod settings.', + 'users.manage' => 'Can manage Castopod users.', + 'persons.manage' => 'Can manage persons.', + 'pages.manage' => 'Can manage pages.', + 'podcasts.view' => 'Can view all podcasts.', + 'podcasts.create' => 'Can create new podcasts.', + 'podcasts.import' => 'Can import podcasts.', + 'fediverse.manage-blocks' => 'Can block fediverse actors/domains from interacting with Castopod.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + 'view' => 'Can view dashboard and analytics of podcast #{id}.', + 'edit' => 'Can edit podcast #{id}.', + 'delete' => 'Can delete podcast #{id}.', + 'manage-import' => 'Can synchronize imported podcast #{id}.', + 'manage-persons' => 'Can manage subscriptions of podcast #{id}.', + 'manage-subscriptions' => 'Can manage subscriptions of podcast #{id}.', + 'manage-contributors' => 'Can manage contributors of podcast #{id}.', + 'manage-platforms' => 'Can set/remove platform links of podcast #{id}.', + 'manage-publications' => 'Can publish podcast #{id}.', + 'manage-notifications' => 'Can view and mark notifications as read for podcast #{id}.', + 'interact-as' => 'Can interact as the podcast #{id} to favourite, share or reply to posts.', + 'episodes' => [ + 'view' => 'Can view dashboards and analytics of podcast #{id}\'s episodes.', + 'create' => 'Can create episodes for podcast #{id}.', + 'edit' => 'Can edit episodes of podcast #{id}.', + 'delete' => 'Can delete episodes of podcast #{id}.', + 'manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + ], + + // missing keys + 'code' => 'Your 6-digit code', + + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Auth/Language/zh-hant/Contributor.php b/modules/Auth/Language/zh-hant/Contributor.php new file mode 100644 index 00000000..c70badc0 --- /dev/null +++ b/modules/Auth/Language/zh-hant/Contributor.php @@ -0,0 +1,47 @@ + 'Podcast contributors', + 'view' => "{username}'s contribution to {podcastTitle}", + 'add' => 'Add contributor', + 'add_contributor' => 'Add a contributor for {0}', + 'edit_role' => 'Update role for {0}', + 'edit' => 'Edit', + 'remove' => 'Remove', + 'list' => [ + 'username' => 'Username', + 'role' => 'Role', + ], + 'form' => [ + 'user' => 'User', + 'user_placeholder' => 'Select a user…', + 'role' => 'Role', + 'role_placeholder' => 'Select its role…', + 'submit_add' => 'Add contributor', + 'submit_edit' => 'Update role', + ], + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', + ], + 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", + 'removeOwnerError' => "You can't remove the podcast owner!", + 'removeSuccess' => + 'You have successfully removed {username} from {podcastTitle}', + 'alreadyAddedError' => + "The contributor you're trying to add has already been added!", + ], +]; diff --git a/modules/Auth/Language/zh-hant/MyAccount.php b/modules/Auth/Language/zh-hant/MyAccount.php new file mode 100644 index 00000000..6ebbb30e --- /dev/null +++ b/modules/Auth/Language/zh-hant/MyAccount.php @@ -0,0 +1,18 @@ + 'My account info', + 'changePassword' => 'Change my password', + 'messages' => [ + 'wrongPasswordError' => "You've entered the wrong password, try again.", + 'passwordChangeSuccess' => 'Password has been successfully changed!', + ], +]; diff --git a/modules/Auth/Language/zh-hant/User.php b/modules/Auth/Language/zh-hant/User.php new file mode 100644 index 00000000..32ec560c --- /dev/null +++ b/modules/Auth/Language/zh-hant/User.php @@ -0,0 +1,60 @@ + "Edit {username}'s role", + 'ban' => 'Ban', + 'unban' => 'Unban', + 'delete' => 'Delete', + 'create' => 'New user', + 'view' => "{username}'s info", + 'all_users' => 'All users', + 'list' => [ + 'user' => 'User', + 'role' => 'Role', + 'banned' => 'Banned?', + ], + 'form' => [ + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + 'new_password' => 'New Password', + 'role' => 'Role', + 'roles' => 'Roles', + 'permissions' => 'Permissions', + 'submit_create' => 'Create user', + 'submit_edit' => 'Save', + 'submit_password_change' => 'Change!', + ], + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', + ], + 'messages' => [ + 'createSuccess' => + 'User created successfully! A welcome email was sent to {username} with a login link, they will be prompted with a password reset upon first authentication.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + 'banSuccess' => '{username} has been banned.', + 'unbanSuccess' => '{username} has been unbanned.', + 'editOwnerError' => + '{username} is the instance owner, one does not simply touch the owner…', + 'banSuperAdminError' => + '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', + 'deleteSuperAdminError' => + '{username} is a superadmin, one does not simply delete a superadmin…', + 'deleteSuccess' => '{username} has been deleted.', + ], +]; diff --git a/modules/Auth/Models/UserModel.php b/modules/Auth/Models/UserModel.php new file mode 100644 index 00000000..16ac112a --- /dev/null +++ b/modules/Auth/Models/UserModel.php @@ -0,0 +1,50 @@ + + */ + protected $allowedFields = [ + 'username', + 'status', + 'status_message', + 'active', + 'is_owner', + 'last_active', + 'deleted_at', + ]; + + /** + * @return User[] + */ + public function getPodcastContributors(int $podcastId): array + { + return $this->select('users.*') + ->join('auth_groups_users', 'users.id = auth_groups_users.user_id') + ->like('auth_groups_users.group', "podcast#{$podcastId}-") + ->findAll(); + } + + public function getPodcastContributor(int $contributorId, int $podcastId): ?User + { + return $this->select('users.*') + ->join('auth_groups_users', 'users.id = auth_groups_users.user_id') + ->where('users.id', $contributorId) + ->like('auth_groups_users.group', "podcast#{$podcastId}-") + ->first(); + } +} diff --git a/modules/Fediverse/ActivityRequest.php b/modules/Fediverse/ActivityRequest.php index cfd268ab..f5b56a29 100644 --- a/modules/Fediverse/ActivityRequest.php +++ b/modules/Fediverse/ActivityRequest.php @@ -14,7 +14,6 @@ use CodeIgniter\HTTP\CURLRequest; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\HTTP\URI; use CodeIgniter\I18n\Time; -use Config\Services; use Modules\Fediverse\Core\Activity; use phpseclib\Crypt\RSA; @@ -33,7 +32,7 @@ class ActivityRequest public function __construct(string $uri, ?string $activityPayload = null) { - $this->request = Services::curlrequest(); + $this->request = service('curlrequest'); if ($activityPayload !== null) { $this->request->setBody($activityPayload); @@ -42,8 +41,8 @@ class ActivityRequest $this->options = [ 'headers' => [ 'Content-Type' => 'application/activity+json', - 'Accept' => 'application/activity+json', - 'User-Agent' => 'Castopod/' . CP_VERSION . '; +' . base_url('', 'https'), + 'Accept' => 'application/activity+json', + 'User-Agent' => 'Castopod/' . CP_VERSION . '; +' . base_url('', 'https'), // TODO: outgoing and incoming requests ], ]; @@ -82,7 +81,7 @@ class ActivityRequest $date = Time::now('GMT')->format('D, d M Y H:i:s T'); $digest = 'SHA-256=' . base64_encode($this->getBodyDigest()); $contentType = $this->options['headers']['Content-Type']; - $contentLength = (string) strlen($this->request->getBody()); + $contentLength = (string) strlen((string) $this->request->getBody()); $userAgent = 'Castopod/' . CP_VERSION . '; +' . base_url('', 'https'); $plainText = "(request-target): post {$path}\nhost: {$host}\ndate: {$date}\ndigest: {$digest}\ncontent-type: {$contentType}\ncontent-length: {$contentLength}\nuser-agent: {$userAgent}"; @@ -98,20 +97,20 @@ class ActivityRequest $this->options = [ 'headers' => [ - 'Content-Type' => $contentType, + 'Content-Type' => $contentType, 'Content-Length' => $contentLength, - 'Authorization' => "Signature {$signatureHeader}", - 'Signature' => $signatureHeader, - 'Host' => $host, - 'Date' => $date, - 'User-Agent' => $userAgent, - 'Digest' => $digest, + 'Authorization' => "Signature {$signatureHeader}", + 'Signature' => $signatureHeader, + 'Host' => $host, + 'Date' => $date, + 'User-Agent' => $userAgent, + 'Digest' => $digest, ], ]; } protected function getBodyDigest(): string { - return hash('sha256', $this->request->getBody(), true); + return hash('sha256', (string) $this->request->getBody(), true); } } diff --git a/modules/Fediverse/Commands/Broadcast.php b/modules/Fediverse/Commands/Broadcast.php new file mode 100644 index 00000000..3e096946 --- /dev/null +++ b/modules/Fediverse/Commands/Broadcast.php @@ -0,0 +1,71 @@ +getScheduledActivities(10); + + foreach ($scheduledActivities as $scheduledActivity) { + // set activity post to processing + model('ActivityModel', false) + ->update($scheduledActivity->id, [ + 'status' => 'processing', + ]); + } + + // Send activity to all followers + foreach ($scheduledActivities as $scheduledActivity) { + try { + if ($scheduledActivity->target_actor_id !== null) { + if ($scheduledActivity->actor_id !== $scheduledActivity->target_actor_id) { + // send activity to targeted actor + send_activity_to_actor( + $scheduledActivity->actor, + $scheduledActivity->targetActor, + json_encode($scheduledActivity->payload, JSON_THROW_ON_ERROR), + ); + } + } else { + // send activity to all actor followers + send_activity_to_followers( + $scheduledActivity->actor, + json_encode($scheduledActivity->payload, JSON_THROW_ON_ERROR), + ); + } + + // set activity post to delivered + model('ActivityModel', false) + ->update($scheduledActivity->id, [ + 'status' => 'delivered', + ]); + + } catch (Exception) { + // set activity post to delivered + model('ActivityModel', false) + ->update($scheduledActivity->id, [ + 'status' => 'failed', + ]); + } + } + } +} diff --git a/modules/Fediverse/Config/Fediverse.php b/modules/Fediverse/Config/Fediverse.php index 382153a8..a35e88dd 100644 --- a/modules/Fediverse/Config/Fediverse.php +++ b/modules/Fediverse/Config/Fediverse.php @@ -10,9 +10,9 @@ declare(strict_types=1); namespace Modules\Fediverse\Config; +use App\Libraries\NoteObject; use CodeIgniter\Config\BaseConfig; use Modules\Fediverse\Objects\ActorObject; -use Modules\Fediverse\Objects\NoteObject; class Fediverse extends BaseConfig { @@ -23,6 +23,7 @@ class Fediverse extends BaseConfig */ public string $actorObject = ActorObject::class; + // FIXME: hotfix applied to have episodes show up in posts public string $noteObject = NoteObject::class; /** @@ -30,16 +31,14 @@ class Fediverse extends BaseConfig * Default avatar and cover images * -------------------------------------------------------------------- */ - public string $defaultAvatarImagePath = 'media/avatar-default.jpg'; + public string $defaultAvatarImagePath = 'avatar-default.jpg'; public string $defaultAvatarImageMimetype = 'image/jpeg'; - public string $defaultCoverImagePath = 'media/banner-default.jpg'; + public string $defaultCoverImagePath = 'banner-default.jpg'; public string $defaultCoverImageMimetype = 'image/jpeg'; - public string $tablesPrefix = 'fediverse_'; - /** * -------------------------------------------------------------------- * Cache options diff --git a/modules/Fediverse/Config/Registrar.php b/modules/Fediverse/Config/Registrar.php new file mode 100644 index 00000000..3c28460f --- /dev/null +++ b/modules/Fediverse/Config/Registrar.php @@ -0,0 +1,22 @@ + + */ + public static function Filters(): array + { + return [ + 'aliases' => [ + 'fediverse' => FediverseFilter::class, + ], + ]; + } +} diff --git a/modules/Fediverse/Config/Routes.php b/modules/Fediverse/Config/Routes.php index 06fa16de..1fb7fe0c 100644 --- a/modules/Fediverse/Config/Routes.php +++ b/modules/Fediverse/Config/Routes.php @@ -2,13 +2,15 @@ declare(strict_types=1); +use CodeIgniter\Router\RouteCollection; + /** * @copyright 2021 Ad Aures * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @link https://castopod.org/ */ -$routes = service('routes'); +/** @var RouteCollection $routes */ $routes->addPlaceholder('actorUsername', '[a-zA-Z0-9\_]{1,32}'); $routes->addPlaceholder( @@ -35,23 +37,22 @@ $routes->group('', [ // Actor $routes->group('@(:actorUsername)', static function ($routes): void { // Actor - $routes->get('/', 'ActorController/$1', [ + $routes->get('/', 'ActorController::index/$1', [ 'as' => 'actor', ]); $routes->post('inbox', 'ActorController::inbox/$1', [ - 'as' => 'inbox', - 'filter' => - 'fediverse:verify-activitystream,verify-blocks,verify-signature', + 'as' => 'inbox', + 'filter' => 'fediverse:verify-activitystream,verify-blocks,verify-signature', ]); $routes->get('outbox', 'ActorController::outbox/$1', [ - 'as' => 'outbox', + 'as' => 'outbox', 'filter' => 'fediverse:verify-activitystream', ]); $routes->get('followers', 'ActorController::followers/$1', [ - 'as' => 'followers', + 'as' => 'followers', 'filter' => 'fediverse::activity-stream', ]); - $routes->post('follow', 'ActorController::attemptFollow/$1', [ + $routes->post('follow', 'ActorController::followAction/$1', [ 'as' => 'attempt-follow', ]); $routes->get('activities/(:uuid)', 'ActorController::activity/$1/$2', [ @@ -59,18 +60,18 @@ $routes->group('', [ ]); }); // Post - $routes->post('posts/new', 'PostController::attemptCreate/$1', [ + $routes->post('posts/create', 'PostController::createAction/$1', [ 'as' => 'post-attempt-create', ]); - $routes->get('posts/(:uuid)', 'PostController/$1', [ + $routes->get('posts/(:uuid)', 'PostController::index/$1', [ 'as' => 'post', ]); - $routes->get('posts/(:uuid)/replies', 'PostController/$1', [ + $routes->get('posts/(:uuid)/replies', 'PostController::index/$1', [ 'as' => 'post-replies', ]); $routes->post( 'posts/(:uuid)/remote/(:postAction)', - 'PostController::attemptRemoteAction/$1/$2/$3', + 'PostController::remoteActionAction/$1/$2/$3', [ 'as' => 'post-attempt-remote-action', ], @@ -78,28 +79,28 @@ $routes->group('', [ // Blocking actors and domains $routes->post( 'fediverse-block-actor', - 'BlockController::attemptBlockActor', + 'BlockController::blockActorAction', [ 'as' => 'fediverse-attempt-block-actor', ], ); $routes->post( 'fediverse-block-domain', - 'BlockController::attemptBlockDomain', + 'BlockController::blockDomainAction', [ 'as' => 'fediverse-attempt-block-domain', ], ); $routes->post( 'fediverse-unblock-actor', - 'BlockController::attemptUnblockActor', + 'BlockController::unblockActorAction', [ 'as' => 'fediverse-attempt-unblock-actor', ], ); $routes->post( 'fediverse-unblock-domain', - 'BlockController::attemptUnblockDomain', + 'BlockController::unblockDomainAction', [ 'as' => 'fediverse-attempt-unblock-domain', ], diff --git a/modules/Fediverse/Controllers/ActivityPubController.php b/modules/Fediverse/Controllers/ActivityPubController.php index fe94c9a1..bceee417 100644 --- a/modules/Fediverse/Controllers/ActivityPubController.php +++ b/modules/Fediverse/Controllers/ActivityPubController.php @@ -11,14 +11,11 @@ declare(strict_types=1); namespace Modules\Fediverse\Controllers; use CodeIgniter\Controller; -use CodeIgniter\HTTP\Response; +use CodeIgniter\HTTP\ResponseInterface; class ActivityPubController extends Controller { - /** - * @noRector ReturnTypeDeclarationRector - */ - public function preflight(): Response + public function preflight(): ResponseInterface { return $this->response->setHeader('Access-Control-Allow-Origin', '*') // for allowing any domain, insecure ->setHeader('Access-Control-Allow-Headers', '*') // for allowing any headers, insecure diff --git a/modules/Fediverse/Controllers/ActorController.php b/modules/Fediverse/Controllers/ActorController.php index 44b0dc3a..a567436b 100644 --- a/modules/Fediverse/Controllers/ActorController.php +++ b/modules/Fediverse/Controllers/ActorController.php @@ -17,15 +17,18 @@ use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\I18n\Time; use Exception; use Modules\Fediverse\Config\Fediverse; +use Modules\Fediverse\Entities\Activity; use Modules\Fediverse\Entities\Actor; use Modules\Fediverse\Entities\Post; +use Modules\Fediverse\Models\ActivityModel; +use Modules\Fediverse\Models\ActorModel; use Modules\Fediverse\Objects\OrderedCollectionObject; use Modules\Fediverse\Objects\OrderedCollectionPage; class ActorController extends Controller { /** - * @var string[] + * @var list */ protected $helpers = ['fediverse']; @@ -45,7 +48,7 @@ class ActorController extends Controller } if ( - ($actor = model('ActorModel', false)->getActorByUsername($params[0])) === null + ! ($actor = model('ActorModel', false)->getActorByUsername($params[0])) instanceof Actor ) { throw PageNotFoundException::forPageNotFound(); } @@ -56,9 +59,6 @@ class ActorController extends Controller return $this->{$method}(...$params); } - /** - * @noRector ReturnTypeDeclarationRector - */ public function index(): ResponseInterface { $actorObjectClass = $this->config->actorObject; @@ -71,8 +71,6 @@ class ActorController extends Controller /** * Handles incoming requests from fediverse servers - * - * @noRector ReturnTypeDeclarationRector */ public function inbox(): ResponseInterface { @@ -93,7 +91,6 @@ class ActorController extends Controller ); // switch/case on activity type - /** @phpstan-ignore-next-line */ switch ($payload->type) { case 'Create': if ($payload->object->type === 'Note') { @@ -106,21 +103,22 @@ class ActorController extends Controller ->getPostByUri($payload->object->inReplyTo); $reply = null; - if ($replyToPost !== null) { + if ($replyToPost instanceof Post) { // TODO: strip content from html to retrieve message // remove all html tags and reconstruct message with mentions? $message = get_message_from_object($payload->object); $reply = new Post([ - 'uri' => $payload->object->id, - 'actor_id' => $payloadActor->id, + 'uri' => $payload->object->id, + 'actor_id' => $payloadActor->id, 'in_reply_to_id' => $replyToPost->id, - 'message' => $message, - 'published_at' => Time::parse($payload->object->published), + 'message' => $message, + 'is_private' => ! is_note_public($payload->object), + 'published_at' => Time::parse($payload->object->published), ]); } - if ($reply !== null) { + if ($reply instanceof Post) { $postId = model('PostModel', false) ->addReply($reply, true, false); @@ -139,9 +137,9 @@ class ActorController extends Controller ->setJSON([]); case 'Delete': $postToDelete = model('PostModel', false) - ->getPostByUri($payload->object->id); + ->getPostByUri($payload->object); - if ($postToDelete !== null) { + if ($postToDelete instanceof Post) { model('PostModel', false) ->removePost($postToDelete, false); } @@ -165,7 +163,7 @@ class ActorController extends Controller $post = model('PostModel', false) ->getPostByUri($payload->object); - if ($post !== null) { + if ($post instanceof Post) { // Like side-effect model('FavouriteModel', false) ->addFavourite($payloadActor, $post, false); @@ -182,7 +180,7 @@ class ActorController extends Controller $post = model('PostModel', false) ->getPostByUri($payload->object); - if ($post !== null) { + if ($post instanceof Post) { model('ActivityModel', false) ->update($activityId, [ 'post_id' => $post->id, @@ -196,7 +194,6 @@ class ActorController extends Controller ->setJSON([]); case 'Undo': // switch/case on the type of activity to undo - /** @phpstan-ignore-next-line */ switch ($payload->object->type) { case 'Follow': // revert side-effect by removing follow from database @@ -210,7 +207,7 @@ class ActorController extends Controller $post = model('PostModel', false) ->getPostByUri($payload->object->object); - if ($post !== null) { + if ($post instanceof Post) { // revert side-effect by removing favourite from database model('FavouriteModel', false) ->removeFavourite($payloadActor, $post, false); @@ -228,10 +225,10 @@ class ActorController extends Controller ->getPostByUri($payload->object->object); $reblogPost = null; - if ($post !== null) { + if ($post instanceof Post) { $reblogPost = model('PostModel', false) ->where([ - 'actor_id' => $payloadActor->id, + 'actor_id' => $payloadActor->id, 'reblog_of_id' => service('uuid') ->fromString($post->id) ->getBytes(), @@ -239,7 +236,7 @@ class ActorController extends Controller ->first(); } - if ($reblogPost !== null) { + if ($reblogPost instanceof \App\Entities\Post) { model('PostModel', false) ->undoReblog($reblogPost, false); @@ -264,12 +261,10 @@ class ActorController extends Controller } } - /** - * @noRector ReturnTypeDeclarationRector - */ public function outbox(): ResponseInterface { // get published activities by publication date + /** @var ActivityModel $actorActivity */ $actorActivity = model('ActivityModel', false) ->where('actor_id', $this->actor->id) ->where('`created_at` <= UTC_TIMESTAMP()', null, false) @@ -282,6 +277,7 @@ class ActorController extends Controller $pager = $actorActivity->pager; $collection = new OrderedCollectionObject(null, $pager); } else { + /** @var Activity[] $paginatedActivity */ $paginatedActivity = $actorActivity->paginate(12, 'default', $pageNumber); $pager = $actorActivity->pager; $orderedItems = []; @@ -297,19 +293,14 @@ class ActorController extends Controller ->setBody($collection->toJSON()); } - /** - * @noRector ReturnTypeDeclarationRector - */ public function followers(): ResponseInterface { - $tablesPrefix = config('Fediverse') - ->tablesPrefix; - // get followers for a specific actor + /** @var ActorModel $followers */ $followers = model('ActorModel', false) - ->join($tablesPrefix . 'follows', $tablesPrefix . 'follows.actor_id = id', 'inner') - ->where($tablesPrefix . 'follows.target_actor_id', $this->actor->id) - ->orderBy($tablesPrefix . 'follows.created_at', 'DESC'); + ->join('fediverse_follows', 'fediverse_follows.actor_id = id', 'inner') + ->where('fediverse_follows.target_actor_id', $this->actor->id) + ->orderBy('fediverse_follows.created_at', 'DESC'); $pageNumber = (int) $this->request->getGet('page'); @@ -318,6 +309,7 @@ class ActorController extends Controller $pager = $followers->pager; $followersCollection = new OrderedCollectionObject(null, $pager); } else { + /** @var Actor[] $paginatedFollowers */ $paginatedFollowers = $followers->paginate(12, 'default', $pageNumber); $pager = $followers->pager; @@ -334,11 +326,10 @@ class ActorController extends Controller ->setBody($followersCollection->toJSON()); } - public function attemptFollow(): RedirectResponse | ResponseInterface + public function followAction(): RedirectResponse { $rules = [ - 'handle' => - 'regex_match[/^@?(?P[\w\.\-]+)@(?P[\w\.\-]+)(?P:[\d]+)?$/]', + 'handle' => 'regex_match[/^@?(?P[\w\.\-]+)@(?P[\w\.\-]+)(?P:[\d]+)?$/]', ]; if (! $this->validate($rules)) { @@ -348,13 +339,15 @@ class ActorController extends Controller ->with('errors', $this->validator->getErrors()); } + $validData = $this->validator->getValidated(); + helper('text'); // get webfinger data from actor // parse actor id to get actor and domain // check if actor and domain exist - $handle = $this->request->getPost('handle'); + $handle = $validData['handle']; $parts = split_handle($handle); try { @@ -382,17 +375,14 @@ class ActorController extends Controller } return redirect()->to( - str_replace('{uri}', urlencode($this->actor->uri), $data->links[$ostatusKey]->template), + str_replace('{uri}', urlencode($this->actor->uri), (string) $data->links[$ostatusKey]->template), ); } - /** - * @noRector ReturnTypeDeclarationRector - */ public function activity(string $activityId): ResponseInterface { if ( - ! ($activity = model('ActivityModel', false)->getActivityById($activityId)) + ! ($activity = model('ActivityModel', false)->getActivityById($activityId)) instanceof Activity ) { throw PageNotFoundException::forPageNotFound(); } diff --git a/modules/Fediverse/Controllers/BlockController.php b/modules/Fediverse/Controllers/BlockController.php index abf6a7c4..e5304b0b 100644 --- a/modules/Fediverse/Controllers/BlockController.php +++ b/modules/Fediverse/Controllers/BlockController.php @@ -17,11 +17,11 @@ use Exception; class BlockController extends Controller { /** - * @var string[] + * @var list */ protected $helpers = ['fediverse']; - public function attemptBlockActor(): RedirectResponse + public function blockActorAction(): RedirectResponse { $rules = [ 'handle' => 'required|regex_match[/^@?([\w\.\-]+)@([\w\.\-]+)(:[\d]+)?$/]', @@ -34,7 +34,9 @@ class BlockController extends Controller ->with('errors', $this->validator->getErrors()); } - $handle = $this->request->getPost('handle'); + $validData = $this->validator->getValidated(); + + $handle = $validData['handle']; if ($parts = split_handle($handle)) { try { @@ -56,7 +58,7 @@ class BlockController extends Controller ])); } - public function attemptUnblockActor(): RedirectResponse + public function unblockActorAction(): RedirectResponse { $rules = [ 'actor_id' => 'required', @@ -69,14 +71,16 @@ class BlockController extends Controller ->with('errors', $this->validator->getErrors()); } + $validData = $this->validator->getValidated(); + model('ActorModel', false) - ->unblockActor((int) $this->request->getPost('actor_id')); + ->unblockActor((int) $validData['actor_id']); return redirect()->back() ->with('message', lang('Fediverse.messages.unblockActorSuccess')); } - public function attemptBlockDomain(): RedirectResponse + public function blockDomainAction(): RedirectResponse { $rules = [ 'domain' => 'required|regex_match[/^[\w\-\.]+[\w]+(:[\d]+)?/]', @@ -89,7 +93,9 @@ class BlockController extends Controller ->with('errors', $this->validator->getErrors()); } - $domain = $this->request->getPost('domain'); + $validData = $this->validator->getValidated(); + + $domain = $validData['domain']; model('BlockedDomainModel', false) ->blockDomain($domain); @@ -99,7 +105,7 @@ class BlockController extends Controller ])); } - public function attemptUnblockDomain(): RedirectResponse + public function unblockDomainAction(): RedirectResponse { $rules = [ 'domain' => 'required', @@ -112,7 +118,9 @@ class BlockController extends Controller ->with('errors', $this->validator->getErrors()); } - $domain = $this->request->getPost('domain'); + $validData = $this->validator->getValidated(); + + $domain = $validData['domain']; model('BlockedDomainModel', false) ->unblockDomain($domain); diff --git a/modules/Fediverse/Controllers/NodeInfo2Controller.php b/modules/Fediverse/Controllers/NodeInfo2Controller.php index 60ef6cb2..4457c600 100644 --- a/modules/Fediverse/Controllers/NodeInfo2Controller.php +++ b/modules/Fediverse/Controllers/NodeInfo2Controller.php @@ -28,19 +28,19 @@ class NodeInfo2Controller extends Controller $nodeInfo2 = [ 'version' => '1.0', - 'server' => [ - 'baseUrl' => base_url(), - 'name' => esc(service('settings') ->get('App.siteName')), + 'server' => [ + 'baseUrl' => base_url(), + 'name' => esc(service('settings') ->get('App.siteName')), 'software' => 'Castopod', - 'version' => CP_VERSION, + 'version' => CP_VERSION, ], - 'protocols' => ['activitypub'], + 'protocols' => ['activitypub'], 'openRegistrations' => config('Auth') ->allowRegistration, 'usage' => [ 'users' => [ - 'total' => $totalUsers, - 'activeMonth' => $activeMonth, + 'total' => $totalUsers, + 'activeMonth' => $activeMonth, 'activeHalfyear' => $activeHalfyear, ], 'localPosts' => $totalPosts, diff --git a/modules/Fediverse/Controllers/PostController.php b/modules/Fediverse/Controllers/PostController.php index 7eb5f47e..8fdd4548 100644 --- a/modules/Fediverse/Controllers/PostController.php +++ b/modules/Fediverse/Controllers/PostController.php @@ -12,8 +12,8 @@ namespace Modules\Fediverse\Controllers; use CodeIgniter\Controller; use CodeIgniter\Exceptions\PageNotFoundException; +use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\HTTP\RedirectResponse; -use CodeIgniter\HTTP\Response; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\I18n\Time; use Modules\Fediverse\Config\Fediverse; @@ -24,7 +24,14 @@ use Modules\Fediverse\Objects\OrderedCollectionPage; class PostController extends Controller { /** - * @var string[] + * Instance of the main Request object. + * + * @var IncomingRequest + */ + protected $request; + + /** + * @var list */ protected $helpers = ['fediverse']; @@ -42,7 +49,11 @@ class PostController extends Controller public function _remap(string $method, string ...$params): mixed { - if (($post = model('PostModel', false)->getPostById($params[0])) === null) { + if ($params === []) { + throw PageNotFoundException::forPageNotFound(); + } + + if (! ($post = model('PostModel', false)->getPostById($params[0])) instanceof Post) { throw PageNotFoundException::forPageNotFound(); } @@ -53,10 +64,7 @@ class PostController extends Controller return $this->{$method}(...$params); } - /** - * @noRector ReturnTypeDeclarationRector - */ - public function index(): Response + public function index(): ResponseInterface { $noteObjectClass = $this->config->noteObject; $noteObject = new $noteObjectClass($this->post); @@ -66,10 +74,7 @@ class PostController extends Controller ->setBody($noteObject->toJSON()); } - /** - * @noRector ReturnTypeDeclarationRector - */ - public function replies(): Response + public function replies(): ResponseInterface { /** * get post replies @@ -92,11 +97,9 @@ class PostController extends Controller $orderedItems = []; $noteObjectClass = $this->config->noteObject; - if ($paginatedReplies !== null) { - foreach ($paginatedReplies as $reply) { - $replyObject = new $noteObjectClass($reply); - $orderedItems[] = $replyObject->toArray(); - } + foreach ($paginatedReplies as $reply) { + $replyObject = new $noteObjectClass($reply); + $orderedItems[] = $replyObject->toArray(); } $collection = new OrderedCollectionPage($pager, $orderedItems); @@ -107,11 +110,11 @@ class PostController extends Controller ->setBody($collection->toJSON()); } - public function attemptCreate(): RedirectResponse + public function createAction(): RedirectResponse { $rules = [ 'actor_id' => 'required|is_natural_no_zero', - 'message' => 'required|max_length[500]', + 'message' => 'required|max_length[500]', ]; if (! $this->validate($rules)) { @@ -121,9 +124,11 @@ class PostController extends Controller ->with('errors', $this->validator->getErrors()); } + $validData = $this->validator->getValidated(); + $newPost = new Post([ - 'actor_id' => $this->request->getPost('actor_id'), - 'message' => $this->request->getPost('message'), + 'actor_id' => $validData['actor_id'], + 'message' => $validData['message'], 'published_at' => Time::now(), ]); @@ -140,7 +145,7 @@ class PostController extends Controller return redirect()->back(); } - public function attemptFavourite(): RedirectResponse + public function favouriteAction(): RedirectResponse { $rules = [ 'actor_id' => 'required|is_natural_no_zero', @@ -153,16 +158,18 @@ class PostController extends Controller ->with('errors', $this->validator->getErrors()); } + $validData = $this->validator->getValidated(); + $actor = model('ActorModel', false) - ->getActorById($this->request->getPost('actor_id')); + ->getActorById($validData['actor_id']); model('FavouriteModel', false) - ->toggleFavourite($actor, $this->post->id); + ->toggleFavourite($actor, $this->post); return redirect()->back(); } - public function attemptReblog(): RedirectResponse + public function reblogAction(): RedirectResponse { $rules = [ 'actor_id' => 'required|is_natural_no_zero', @@ -175,8 +182,10 @@ class PostController extends Controller ->with('errors', $this->validator->getErrors()); } + $validData = $this->validator->getValidated(); + $actor = model('ActorModel', false) - ->getActorById($this->request->getPost('actor_id')); + ->getActorById($validData['actor_id']); model('PostModel', false) ->toggleReblog($actor, $this->post); @@ -184,11 +193,11 @@ class PostController extends Controller return redirect()->back(); } - public function attemptReply(): RedirectResponse + public function replyAction(): RedirectResponse { $rules = [ 'actor_id' => 'required|is_natural_no_zero', - 'message' => 'required|max_length[500]', + 'message' => 'required|max_length[500]', ]; if (! $this->validate($rules)) { @@ -198,11 +207,13 @@ class PostController extends Controller ->with('errors', $this->validator->getErrors()); } + $validData = $this->validator->getValidated(); + $newReplyPost = new Post([ - 'actor_id' => $this->request->getPost('actor_id'), + 'actor_id' => $validData['actor_id'], 'in_reply_to_id' => $this->post->id, - 'message' => $this->request->getPost('message'), - 'published_at' => Time::now(), + 'message' => $validData['message'], + 'published_at' => Time::now(), ]); if (! model('PostModel', false)->addReply($newReplyPost)) { @@ -217,11 +228,10 @@ class PostController extends Controller return redirect()->back(); } - public function attemptRemoteAction(string $action): RedirectResponse | ResponseInterface + public function remoteActionAction(string $action): RedirectResponse | ResponseInterface { $rules = [ - 'handle' => - 'regex_match[/^@?(?P[\w\.\-]+)@(?P[\w\.\-]+)(?P:[\d]+)?$/]', + 'handle' => 'regex_match[/^@?(?P[\w\.\-]+)@(?P[\w\.\-]+)(?P:[\d]+)?$/]', ]; if (! $this->validate($rules)) { @@ -231,13 +241,15 @@ class PostController extends Controller ->with('errors', $this->validator->getErrors()); } + $validData = $this->validator->getValidated(); + helper('text'); // get webfinger data from actor // parse actor id to get actor and domain // check if actor and domain exist if ( - ! ($parts = split_handle($this->request->getPost('handle'))) || + ! ($parts = split_handle($validData['handle'])) || ! ($data = get_webfinger_data($parts['username'], $parts['domain'])) ) { return redirect() @@ -259,25 +271,25 @@ class PostController extends Controller } return redirect()->to( - str_replace('{uri}', urlencode($this->post->uri), $data->links[$ostatusKey]->template), + str_replace('{uri}', urlencode($this->post->uri), (string) $data->links[$ostatusKey]->template), ); } - public function attemptBlockActor(): RedirectResponse + public function blockActorAction(): RedirectResponse { model('ActorModel', false)->blockActor($this->post->actor->id); return redirect()->back(); } - public function attemptBlockDomain(): RedirectResponse + public function blockDomainAction(): RedirectResponse { model('BlockedDomainModel', false)->blockDomain($this->post->actor->domain); return redirect()->back(); } - public function attemptDelete(): RedirectResponse + public function deleteAction(): RedirectResponse { model('PostModel', false)->removePost($this->post); diff --git a/modules/Fediverse/Controllers/SchedulerController.php b/modules/Fediverse/Controllers/SchedulerController.php deleted file mode 100644 index 15a02c0b..00000000 --- a/modules/Fediverse/Controllers/SchedulerController.php +++ /dev/null @@ -1,54 +0,0 @@ -getScheduledActivities(); - - // Send activity to all followers - foreach ($scheduledActivities as $scheduledActivity) { - if ($scheduledActivity->target_actor_id !== null) { - if ($scheduledActivity->actor_id !== $scheduledActivity->target_actor_id) { - // send activity to targeted actor - send_activity_to_actor( - $scheduledActivity->actor, - $scheduledActivity->targetActor, - json_encode($scheduledActivity->payload, JSON_THROW_ON_ERROR) - ); - } - } else { - // send activity to all actor followers - send_activity_to_followers( - $scheduledActivity->actor, - json_encode($scheduledActivity->payload, JSON_THROW_ON_ERROR), - ); - } - - // set activity post to delivered - model('ActivityModel', false) - ->update($scheduledActivity->id, [ - 'status' => 'delivered', - ]); - } - } -} diff --git a/modules/Fediverse/Core/AbstractObject.php b/modules/Fediverse/Core/AbstractObject.php index 85a837c5..81a21b05 100644 --- a/modules/Fediverse/Core/AbstractObject.php +++ b/modules/Fediverse/Core/AbstractObject.php @@ -24,7 +24,7 @@ abstract class AbstractObject } /** - * @return array + * @return array */ public function toArray(): array { @@ -42,13 +42,11 @@ abstract class AbstractObject } // removes all NULL, FALSE and Empty Strings but leaves 0 (zero) values - return array_filter($array, static function ($value): bool { - return $value !== null && $value !== false && $value !== ''; - }); + return array_filter($array, static fn ($value): bool => ! in_array($value, [null, false, ''], true)); } - public function toJSON(): string | bool + public function toJSON(): string { - return json_encode($this->toArray(), JSON_UNESCAPED_UNICODE); + return (string) json_encode($this->toArray(), JSON_UNESCAPED_UNICODE); } } diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-010000_add_actors.php b/modules/Fediverse/Database/Migrations/2018-01-01-010000_add_actors.php index 0293c1b9..dc323b9d 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-010000_add_actors.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-010000_add_actors.php @@ -12,28 +12,30 @@ declare(strict_types=1); namespace Modules\Fediverse\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddActors extends Migration +class AddActors extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'id' => [ - 'type' => 'INT', - 'unsigned' => true, + 'type' => 'INT', + 'unsigned' => true, 'auto_increment' => true, ], 'uri' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 255, ], 'username' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 32, ], 'domain' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 255, ], 'private_key' => [ @@ -45,7 +47,7 @@ class AddActors extends Migration 'null' => true, ], 'display_name' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 128, ], 'summary' => [ @@ -53,55 +55,55 @@ class AddActors extends Migration 'null' => true, ], 'avatar_image_url' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 255, - 'null' => true, + 'null' => true, ], // constraint is 13 because the longest safe mimetype for images is image/svg+xml, // see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types 'avatar_image_mimetype' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 13, - 'null' => true, + 'null' => true, ], 'cover_image_url' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 255, - 'null' => true, + 'null' => true, ], 'cover_image_mimetype' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 13, - 'null' => true, + 'null' => true, ], 'inbox_url' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 255, ], 'outbox_url' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 255, - 'null' => true, + 'null' => true, ], 'followers_url' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 255, - 'null' => true, + 'null' => true, ], 'followers_count' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'default' => 0, + 'default' => 0, ], 'posts_count' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'default' => 0, + 'default' => 0, ], 'is_blocked' => [ - 'type' => 'TINYINT', + 'type' => 'TINYINT', 'constraint' => 1, - 'default' => 0, + 'default' => 0, ], 'created_at' => [ 'type' => 'DATETIME', @@ -113,11 +115,12 @@ class AddActors extends Migration $this->forge->addPrimaryKey('id'); $this->forge->addUniqueKey('uri'); $this->forge->addUniqueKey(['username', 'domain']); - $this->forge->createTable(config('Fediverse')->tablesPrefix . 'actors'); + $this->forge->createTable('fediverse_actors'); } + #[Override] public function down(): void { - $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'actors'); + $this->forge->dropTable('fediverse_actors'); } } diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-020000_add_posts.php b/modules/Fediverse/Database/Migrations/2018-01-01-020000_add_posts.php index e69e6bfa..c6bffce5 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-020000_add_posts.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-020000_add_posts.php @@ -12,59 +12,61 @@ declare(strict_types=1); namespace Modules\Fediverse\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddPosts extends Migration +class AddPosts extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'id' => [ - 'type' => 'BINARY', + 'type' => 'BINARY', 'constraint' => 16, ], 'uri' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 255, ], 'actor_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'in_reply_to_id' => [ - 'type' => 'BINARY', + 'type' => 'BINARY', 'constraint' => 16, - 'null' => true, + 'null' => true, ], 'reblog_of_id' => [ - 'type' => 'BINARY', + 'type' => 'BINARY', 'constraint' => 16, - 'null' => true, + 'null' => true, ], 'message' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 500, - 'null' => true, + 'null' => true, ], 'message_html' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 600, - 'null' => true, + 'null' => true, ], 'favourites_count' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'default' => 0, + 'default' => 0, ], 'reblogs_count' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'default' => 0, + 'default' => 0, ], 'replies_count' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'default' => 0, + 'default' => 0, ], 'published_at' => [ 'type' => 'DATETIME', @@ -75,21 +77,19 @@ class AddPosts extends Migration ], ]); - $tablesPrefix = config('Fediverse') - ->tablesPrefix; - $this->forge->addPrimaryKey('id'); $this->forge->addUniqueKey('uri'); // FIXME: an actor must reblog a post only once // $this->forge->addUniqueKey(['actor_id', 'reblog_of_id']); - $this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE'); - $this->forge->addForeignKey('in_reply_to_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE'); - $this->forge->addForeignKey('reblog_of_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE'); - $this->forge->createTable($tablesPrefix . 'posts'); + $this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE'); + $this->forge->addForeignKey('in_reply_to_id', 'fediverse_posts', 'id', '', 'CASCADE'); + $this->forge->addForeignKey('reblog_of_id', 'fediverse_posts', 'id', '', 'CASCADE'); + $this->forge->createTable('fediverse_posts'); } + #[Override] public function down(): void { - $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'posts'); + $this->forge->dropTable('fediverse_posts'); } } diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_activities.php b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_activities.php index eccc8026..b497009b 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_activities.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_activities.php @@ -12,42 +12,44 @@ declare(strict_types=1); namespace Modules\Fediverse\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddActivities extends Migration +class AddActivities extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'id' => [ - 'type' => 'BINARY', + 'type' => 'BINARY', 'constraint' => 16, ], 'actor_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'target_actor_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'null' => true, + 'null' => true, ], 'post_id' => [ - 'type' => 'BINARY', + 'type' => 'BINARY', 'constraint' => 16, - 'null' => true, + 'null' => true, ], 'type' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 100, ], 'payload' => [ 'type' => 'JSON', ], 'status' => [ - 'type' => 'ENUM', + 'type' => 'ENUM', 'constraint' => ['queued', 'delivered'], - 'null' => true, + 'null' => true, ], 'scheduled_at' => [ 'type' => 'DATETIME', @@ -58,18 +60,16 @@ class AddActivities extends Migration ], ]); - $tablesPrefix = config('Fediverse') - ->tablesPrefix; - $this->forge->addPrimaryKey('id'); - $this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE'); - $this->forge->addForeignKey('target_actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE'); - $this->forge->addForeignKey('post_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE'); - $this->forge->createTable($tablesPrefix . 'activities'); + $this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE'); + $this->forge->addForeignKey('target_actor_id', 'fediverse_actors', 'id', '', 'CASCADE'); + $this->forge->addForeignKey('post_id', 'fediverse_posts', 'id', '', 'CASCADE'); + $this->forge->createTable('fediverse_activities'); } + #[Override] public function down(): void { - $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'activities'); + $this->forge->dropTable('fediverse_activities'); } } diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_favourites.php b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_favourites.php index 23554047..be9b5755 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_favourites.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_favourites.php @@ -12,35 +12,35 @@ declare(strict_types=1); namespace Modules\Fediverse\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddFavourites extends Migration +class AddFavourites extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'actor_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'post_id' => [ - 'type' => 'BINARY', + 'type' => 'BINARY', 'constraint' => 16, ], ]); - $tablesPrefix = config('Fediverse') - ->tablesPrefix; - $this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()'); $this->forge->addPrimaryKey(['actor_id', 'post_id']); - $this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE'); - $this->forge->addForeignKey('post_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE'); - $this->forge->createTable($tablesPrefix . 'favourites'); + $this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE'); + $this->forge->addForeignKey('post_id', 'fediverse_posts', 'id', '', 'CASCADE'); + $this->forge->createTable('fediverse_favourites'); } + #[Override] public function down(): void { - $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'favourites'); + $this->forge->dropTable('fediverse_favourites'); } } diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_follows.php b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_follows.php index 8f7c6f2d..945933f4 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_follows.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_follows.php @@ -12,37 +12,37 @@ declare(strict_types=1); namespace Modules\Fediverse\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddFollowers extends Migration +class AddFollowers extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'actor_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'comment' => 'Actor that is following', + 'comment' => 'Actor that is following', ], 'target_actor_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'comment' => 'Actor that is followed', + 'comment' => 'Actor that is followed', ], ]); - $tablesPrefix = config('Fediverse') - ->tablesPrefix; - $this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()'); $this->forge->addPrimaryKey(['actor_id', 'target_actor_id']); - $this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE'); - $this->forge->addForeignKey('target_actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE'); - $this->forge->createTable($tablesPrefix . 'follows'); + $this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE'); + $this->forge->addForeignKey('target_actor_id', 'fediverse_actors', 'id', '', 'CASCADE'); + $this->forge->createTable('fediverse_follows'); } + #[Override] public function down(): void { - $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'follows'); + $this->forge->dropTable('fediverse_follows'); } } diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_preview_cards.php b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_preview_cards.php index a2b1d51d..479d7102 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_preview_cards.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_preview_cards.php @@ -12,54 +12,56 @@ declare(strict_types=1); namespace Modules\Fediverse\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddPreviewCards extends Migration +class AddPreviewCards extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'id' => [ - 'type' => 'INT', - 'unsigned' => true, + 'type' => 'INT', + 'unsigned' => true, 'auto_increment' => true, ], 'url' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 512, ], 'title' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 128, ], 'description' => [ 'type' => 'TEXT', ], 'type' => [ - 'type' => 'ENUM', + 'type' => 'ENUM', 'constraint' => ['link', 'video', 'image', 'rich'], - 'default' => 'link', + 'default' => 'link', ], 'author_name' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 64, - 'null' => true, + 'null' => true, ], 'author_url' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 255, - 'null' => true, + 'null' => true, ], 'provider_name' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 255, ], 'provider_url' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 255, ], 'image' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 255, ], 'html' => [ @@ -75,11 +77,12 @@ class AddPreviewCards extends Migration $this->forge->addPrimaryKey('id'); $this->forge->addUniqueKey('url'); - $this->forge->createTable(config('Fediverse')->tablesPrefix . 'preview_cards'); + $this->forge->createTable('fediverse_preview_cards'); } + #[Override] public function down(): void { - $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'preview_cards'); + $this->forge->dropTable('fediverse_preview_cards'); } } diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-110000_add_posts_preview_cards.php b/modules/Fediverse/Database/Migrations/2018-01-01-110000_add_posts_preview_cards.php index df0eab8d..3d8d4d70 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-110000_add_posts_preview_cards.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-110000_add_posts_preview_cards.php @@ -12,34 +12,34 @@ declare(strict_types=1); namespace Modules\Fediverse\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddPostsPreviewCards extends Migration +class AddPostsPreviewCards extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'post_id' => [ - 'type' => 'BINARY', + 'type' => 'BINARY', 'constraint' => 16, ], 'preview_card_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], ]); - $tablesPrefix = config('Fediverse') - ->tablesPrefix; - $this->forge->addPrimaryKey(['post_id', 'preview_card_id']); - $this->forge->addForeignKey('post_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE'); - $this->forge->addForeignKey('preview_card_id', $tablesPrefix . 'preview_cards', 'id', '', 'CASCADE'); - $this->forge->createTable($tablesPrefix . 'posts_preview_cards'); + $this->forge->addForeignKey('post_id', 'fediverse_posts', 'id', '', 'CASCADE'); + $this->forge->addForeignKey('preview_card_id', 'fediverse_preview_cards', 'id', '', 'CASCADE'); + $this->forge->createTable('fediverse_posts_preview_cards'); } + #[Override] public function down(): void { - $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'posts_preview_cards'); + $this->forge->dropTable('fediverse_posts_preview_cards'); } } diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-120000_add_blocked_domains.php b/modules/Fediverse/Database/Migrations/2018-01-01-120000_add_blocked_domains.php index 6906866f..d17079a1 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-120000_add_blocked_domains.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-120000_add_blocked_domains.php @@ -12,15 +12,17 @@ declare(strict_types=1); namespace Modules\Fediverse\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddBlockedDomains extends Migration +class AddBlockedDomains extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'name' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 255, ], 'created_at' => [ @@ -28,11 +30,12 @@ class AddBlockedDomains extends Migration ], ]); $this->forge->addPrimaryKey('name'); - $this->forge->createTable(config('Fediverse')->tablesPrefix . 'blocked_domains'); + $this->forge->createTable('fediverse_blocked_domains'); } + #[Override] public function down(): void { - $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'blocked_domains'); + $this->forge->dropTable('fediverse_blocked_domains'); } } diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-130000_add_notifications.php b/modules/Fediverse/Database/Migrations/2018-01-01-130000_add_notifications.php index 7fa59da2..a1764482 100644 --- a/modules/Fediverse/Database/Migrations/2018-01-01-130000_add_notifications.php +++ b/modules/Fediverse/Database/Migrations/2018-01-01-130000_add_notifications.php @@ -12,37 +12,38 @@ declare(strict_types=1); namespace App\Database\Migrations; -use CodeIgniter\Database\Migration; +use Override; -class AddNotifications extends Migration +class AddNotifications extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'id' => [ - 'type' => 'INT', - 'unsigned' => true, + 'type' => 'INT', + 'unsigned' => true, 'auto_increment' => true, ], 'actor_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'target_actor_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'post_id' => [ - 'type' => 'BINARY', + 'type' => 'BINARY', 'constraint' => 16, - 'null' => true, + 'null' => true, ], 'activity_id' => [ - 'type' => 'BINARY', + 'type' => 'BINARY', 'constraint' => 16, ], 'type' => [ - 'type' => 'ENUM', + 'type' => 'ENUM', 'constraint' => ['like', 'follow', 'share', 'reply'], ], 'read_at' => [ @@ -57,19 +58,17 @@ class AddNotifications extends Migration ], ]); - $tablesPrefix = config('Fediverse') - ->tablesPrefix; - $this->forge->addPrimaryKey('id'); - $this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE'); - $this->forge->addForeignKey('target_actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE'); - $this->forge->addForeignKey('post_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE'); - $this->forge->addForeignKey('activity_id', $tablesPrefix . 'activities', 'id', '', 'CASCADE'); - $this->forge->createTable($tablesPrefix . 'notifications'); + $this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE'); + $this->forge->addForeignKey('target_actor_id', 'fediverse_actors', 'id', '', 'CASCADE'); + $this->forge->addForeignKey('post_id', 'fediverse_posts', 'id', '', 'CASCADE'); + $this->forge->addForeignKey('activity_id', 'fediverse_activities', 'id', '', 'CASCADE'); + $this->forge->createTable('fediverse_notifications'); } + #[Override] public function down(): void { - $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'notifications'); + $this->forge->dropTable('fediverse_notifications'); } } diff --git a/modules/Fediverse/Database/Migrations/2018-01-02-120000_update_activities_status.php b/modules/Fediverse/Database/Migrations/2018-01-02-120000_update_activities_status.php new file mode 100644 index 00000000..2d7adac0 --- /dev/null +++ b/modules/Fediverse/Database/Migrations/2018-01-02-120000_update_activities_status.php @@ -0,0 +1,44 @@ + [ + 'type' => 'ENUM', + 'constraint' => ['queued', 'processing', 'delivered', 'failed'], + 'null' => true, + ], + ]; + + $this->forge->modifyColumn('fediverse_activities', $fields); + } + + public function down(): void + { + $fields = [ + 'status' => [ + 'type' => 'ENUM', + 'constraint' => ['queued', 'delivered'], + 'null' => true, + ], + ]; + + $this->forge->modifyColumn('fediverse_activities', $fields); + } +} diff --git a/modules/Fediverse/Database/Migrations/2025-07-31-120000_add_is_private_to_posts.php b/modules/Fediverse/Database/Migrations/2025-07-31-120000_add_is_private_to_posts.php new file mode 100644 index 00000000..d1ac9327 --- /dev/null +++ b/modules/Fediverse/Database/Migrations/2025-07-31-120000_add_is_private_to_posts.php @@ -0,0 +1,35 @@ + [ + 'type' => 'TINYINT', + 'constraint' => 1, + 'default' => 0, + 'after' => 'message_html', + ], + ]; + + $this->forge->addColumn('fediverse_posts', $fields); + } + + public function down(): void + { + $this->forge->dropColumn('fediverse_posts', 'is_private'); + } +} diff --git a/modules/Fediverse/Entities/Activity.php b/modules/Fediverse/Entities/Activity.php index 3e2107f7..34d34cec 100644 --- a/modules/Fediverse/Entities/Activity.php +++ b/modules/Fediverse/Entities/Activity.php @@ -10,17 +10,18 @@ declare(strict_types=1); namespace Modules\Fediverse\Entities; +use CodeIgniter\I18n\Time; use Michalsn\Uuid\UuidEntity; use RuntimeException; /** * @property string $id * @property int $actor_id - * @property Actor $actor + * @property ?Actor $actor * @property int|null $target_actor_id - * @property Actor $target_actor + * @property ?Actor $target_actor * @property string|null $post_id - * @property Post $post + * @property ?Post $post * @property string $type * @property object $payload * @property string|null $status @@ -41,7 +42,7 @@ class Activity extends UuidEntity protected $uuids = ['id', 'post_id']; /** - * @var string[] + * @var list */ protected $dates = ['scheduled_at', 'created_at']; @@ -49,22 +50,18 @@ class Activity extends UuidEntity * @var array */ protected $casts = [ - 'id' => 'string', - 'actor_id' => 'integer', + 'id' => 'string', + 'actor_id' => 'integer', 'target_actor_id' => '?integer', - 'post_id' => '?string', - 'type' => 'string', - 'payload' => 'json', - 'status' => '?string', + 'post_id' => '?string', + 'type' => 'string', + 'payload' => 'json', + 'status' => '?string', ]; public function getActor(): Actor { - if ($this->actor_id === null) { - throw new RuntimeException('Activity must have an actor_id before getting the actor.'); - } - - if ($this->actor === null) { + if (! $this->actor instanceof Actor) { $this->actor = model('ActorModel', false) ->getActorById($this->actor_id); } @@ -78,7 +75,7 @@ class Activity extends UuidEntity throw new RuntimeException('Activity must have a target_actor_id before getting the target actor.'); } - if ($this->target_actor === null) { + if (! $this->target_actor instanceof Actor) { $this->target_actor = model('ActorModel', false) ->getActorById($this->target_actor_id); } @@ -92,7 +89,7 @@ class Activity extends UuidEntity throw new RuntimeException('Activity must have a post_id before getting post.'); } - if ($this->post === null) { + if (! $this->post instanceof Post) { $this->post = model('PostModel', false) ->getPostById($this->post_id); } diff --git a/modules/Fediverse/Entities/Actor.php b/modules/Fediverse/Entities/Actor.php index d7a5ba64..9b6bdb42 100644 --- a/modules/Fediverse/Entities/Actor.php +++ b/modules/Fediverse/Entities/Actor.php @@ -11,7 +11,6 @@ declare(strict_types=1); namespace Modules\Fediverse\Entities; use CodeIgniter\Entity\Entity; -use RuntimeException; /** * @property int $id @@ -42,7 +41,7 @@ class Actor extends Entity protected string $public_key_id; /** - * @var \Modules\Fediverse\Entities\Actor[]|null + * @var Actor[]|null */ protected ?array $followers = null; @@ -52,24 +51,24 @@ class Actor extends Entity * @var array */ protected $casts = [ - 'id' => 'integer', - 'uri' => 'string', - 'username' => 'string', - 'domain' => 'string', - 'display_name' => 'string', - 'summary' => '?string', - 'private_key' => '?string', - 'public_key' => '?string', - 'avatar_image_url' => '?string', + 'id' => 'integer', + 'uri' => 'string', + 'username' => 'string', + 'domain' => 'string', + 'display_name' => 'string', + 'summary' => '?string', + 'private_key' => '?string', + 'public_key' => '?string', + 'avatar_image_url' => '?string', 'avatar_image_mimetype' => '?string', - 'cover_image_url' => '?string', - 'cover_image_mimetype' => '?string', - 'inbox_url' => 'string', - 'outbox_url' => '?string', - 'followers_url' => '?string', - 'followers_count' => 'integer', - 'posts_count' => 'integer', - 'is_blocked' => 'boolean', + 'cover_image_url' => '?string', + 'cover_image_mimetype' => '?string', + 'inbox_url' => 'string', + 'outbox_url' => '?string', + 'followers_url' => '?string', + 'followers_count' => 'integer', + 'posts_count' => 'integer', + 'is_blocked' => 'boolean', ]; public function getPublicKeyId(): string @@ -96,12 +95,8 @@ class Actor extends Entity */ public function getFollowers(): array { - if ($this->id === null) { - throw new RuntimeException('Actor must be created before getting followers.'); - } - if ($this->followers === null) { - $this->followers = (array) model('ActorModel', false) + $this->followers = model('ActorModel', false) ->getFollowers($this->id); } diff --git a/modules/Fediverse/Entities/Favourite.php b/modules/Fediverse/Entities/Favourite.php index 2625e6cb..1811d8f5 100644 --- a/modules/Fediverse/Entities/Favourite.php +++ b/modules/Fediverse/Entities/Favourite.php @@ -28,6 +28,6 @@ class Favourite extends UuidEntity */ protected $casts = [ 'actor_id' => 'integer', - 'post_id' => 'string', + 'post_id' => 'string', ]; } diff --git a/modules/Fediverse/Entities/Follow.php b/modules/Fediverse/Entities/Follow.php index 2a415e89..49736eea 100644 --- a/modules/Fediverse/Entities/Follow.php +++ b/modules/Fediverse/Entities/Follow.php @@ -22,7 +22,7 @@ class Follow extends Entity * @var array */ protected $casts = [ - 'actor_id' => 'integer', + 'actor_id' => 'integer', 'target_actor_id' => 'integer', ]; } diff --git a/modules/Fediverse/Entities/Notification.php b/modules/Fediverse/Entities/Notification.php index cfd74184..0815c273 100644 --- a/modules/Fediverse/Entities/Notification.php +++ b/modules/Fediverse/Entities/Notification.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace Modules\Fediverse\Entities; +use CodeIgniter\I18n\Time; use Michalsn\Uuid\UuidEntity; use Modules\Fediverse\Models\ActorModel; use Modules\Fediverse\Models\PostModel; @@ -18,11 +19,11 @@ use RuntimeException; /** * @property int $id * @property int $actor_id - * @property Actor $actor + * @property ?Actor $actor * @property int $target_actor_id - * @property Actor $target_actor + * @property ?Actor $target_actor * @property string|null $post_id - * @property Post $post + * @property ?Post $post * @property string $activity_id * @property Activity $activity * @property 'like'|'follow'|'share'|'reply' $type @@ -46,7 +47,7 @@ class Notification extends UuidEntity protected $uuids = ['post_id', 'activity_id']; /** - * @var string[] + * @var list */ protected $dates = ['read_at', 'created_at', 'updated_at']; @@ -54,22 +55,19 @@ class Notification extends UuidEntity * @var array */ protected $casts = [ - 'id' => 'integer', - 'actor_id' => 'integer', + 'id' => 'integer', + 'actor_id' => 'integer', 'target_actor_id' => 'integer', - 'post_id' => '?string', - 'activity_id' => 'string', - 'type' => 'string', + 'post_id' => '?string', + 'activity_id' => 'string', + 'type' => 'string', ]; public function getActor(): ?Actor { - if ($this->actor_id === null) { - throw new RuntimeException('Notification must have an actor_id before getting actor.'); - } - if (! $this->actor instanceof Actor) { - $this->actor = (new ActorModel())->getActorById($this->actor_id); + $this->actor = new ActorModel() + ->getActorById($this->actor_id); } return $this->actor; @@ -77,12 +75,9 @@ class Notification extends UuidEntity public function getTargetActor(): ?Actor { - if ($this->target_actor_id === null) { - throw new RuntimeException('Notification must have a target_actor_id before getting target actor.'); - } - if (! $this->target_actor instanceof Actor) { - $this->target_actor = (new ActorModel())->getActorById($this->target_actor_id); + $this->target_actor = new ActorModel() + ->getActorById($this->target_actor_id); } return $this->target_actor; @@ -95,7 +90,8 @@ class Notification extends UuidEntity } if (! $this->post instanceof Post) { - $this->post = (new PostModel())->getPostById($this->post_id); + $this->post = new PostModel() + ->getPostById($this->post_id); } return $this->post; diff --git a/modules/Fediverse/Entities/Post.php b/modules/Fediverse/Entities/Post.php index 45aab89e..10d41a2a 100644 --- a/modules/Fediverse/Entities/Post.php +++ b/modules/Fediverse/Entities/Post.php @@ -18,16 +18,19 @@ use RuntimeException; * @property string $id * @property string $uri * @property int $actor_id - * @property Actor $actor + * @property ?Actor $actor * @property string|null $in_reply_to_id * @property Post|null $reply_to_post * @property string|null $reblog_of_id * @property Post|null $reblog_of_post * @property string $message * @property string $message_html + * @property bool $is_private + * * @property int $favourites_count * @property int $reblogs_count * @property int $replies_count + * * @property Time $published_at * @property Time $created_at * @@ -65,7 +68,7 @@ class Post extends UuidEntity protected $uuids = ['id', 'in_reply_to_id', 'reblog_of_id']; /** - * @var string[] + * @var list */ protected $dates = ['published_at', 'created_at']; @@ -73,16 +76,17 @@ class Post extends UuidEntity * @var array */ protected $casts = [ - 'id' => 'string', - 'uri' => 'string', - 'actor_id' => 'integer', - 'in_reply_to_id' => '?string', - 'reblog_of_id' => '?string', - 'message' => 'string', - 'message_html' => 'string', + 'id' => 'string', + 'uri' => 'string', + 'actor_id' => 'integer', + 'in_reply_to_id' => '?string', + 'reblog_of_id' => '?string', + 'message' => 'string', + 'message_html' => 'string', + 'is_private' => 'boolean', 'favourites_count' => 'integer', - 'reblogs_count' => 'integer', - 'replies_count' => 'integer', + 'reblogs_count' => 'integer', + 'replies_count' => 'integer', ]; /** @@ -90,11 +94,7 @@ class Post extends UuidEntity */ public function getActor(): Actor { - if ($this->actor_id === null) { - throw new RuntimeException('Post must have an actor_id before getting actor.'); - } - - if ($this->actor === null) { + if (! $this->actor instanceof Actor) { $this->actor = model('ActorModel', false) ->getActorById($this->actor_id); } @@ -104,11 +104,7 @@ class Post extends UuidEntity public function getPreviewCard(): ?PreviewCard { - if ($this->id === null) { - throw new RuntimeException('Post must be created before getting preview_card.'); - } - - if ($this->preview_card === null) { + if (! $this->preview_card instanceof PreviewCard) { $this->preview_card = model('PreviewCardModel', false) ->getPostPreviewCard($this->id); } @@ -121,12 +117,8 @@ class Post extends UuidEntity */ public function getReplies(): array { - if ($this->id === null) { - throw new RuntimeException('Post must be created before getting replies.'); - } - if ($this->replies === null) { - $this->replies = (array) model('PostModel', false) + $this->replies = model('PostModel', false) ->getPostReplies($this->id); } @@ -144,7 +136,7 @@ class Post extends UuidEntity throw new RuntimeException('Post is not a reply.'); } - if ($this->reply_to_post === null) { + if (! $this->reply_to_post instanceof self) { $this->reply_to_post = model('PostModel', false) ->getPostById($this->in_reply_to_id); } @@ -157,12 +149,8 @@ class Post extends UuidEntity */ public function getReblogs(): array { - if ($this->id === null) { - throw new RuntimeException('Post must be created before getting reblogs.'); - } - if ($this->reblogs === null) { - $this->reblogs = (array) model('PostModel', false) + $this->reblogs = model('PostModel', false) ->getPostReblogs($this->id); } @@ -175,7 +163,7 @@ class Post extends UuidEntity throw new RuntimeException('Post is not a reblog.'); } - if ($this->reblog_of_post === null) { + if (! $this->reblog_of_post instanceof self) { $this->reblog_of_post = model('PostModel', false) ->getPostById($this->reblog_of_id); } diff --git a/modules/Fediverse/Entities/PreviewCard.php b/modules/Fediverse/Entities/PreviewCard.php index ba8d8b88..2271aca4 100644 --- a/modules/Fediverse/Entities/PreviewCard.php +++ b/modules/Fediverse/Entities/PreviewCard.php @@ -32,17 +32,17 @@ class PreviewCard extends Entity * @var array */ protected $casts = [ - 'id' => 'integer', - 'post_id' => 'string', - 'url' => 'string', - 'title' => 'string', - 'description' => 'string', - 'type' => 'string', - 'author_name' => '?string', - 'author_url' => '?string', + 'id' => 'integer', + 'post_id' => 'string', + 'url' => 'string', + 'title' => 'string', + 'description' => 'string', + 'type' => 'string', + 'author_name' => '?string', + 'author_url' => '?string', 'provider_name' => '?string', - 'provider_url' => '?string', - 'image' => '?string', - 'html' => '?string', + 'provider_url' => '?string', + 'image' => '?string', + 'html' => '?string', ]; } diff --git a/modules/Fediverse/Filters/FediverseFilter.php b/modules/Fediverse/Filters/FediverseFilter.php index ddac50de..2e9b32ba 100644 --- a/modules/Fediverse/Filters/FediverseFilter.php +++ b/modules/Fediverse/Filters/FediverseFilter.php @@ -9,29 +9,26 @@ use CodeIgniter\Filters\FilterInterface; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\HTTP\URI; -use Config\Services; use Exception; use Modules\Fediverse\HttpSignature; +use Override; class FediverseFilter implements FilterInterface { /** - * Do whatever processing this filter needs to do. By default it should not return anything during normal execution. - * However, when an abnormal state is found, it should return an instance of CodeIgniter\HTTP\Response. If it does, - * script execution will end and that Response will be sent back to the client, allowing for error pages, redirects, - * etc. + * @param string[]|null $params * - * @param string[]|null $params - * @return void|mixed + * @return RequestInterface|ResponseInterface|string|null */ + #[Override] public function before(RequestInterface $request, $params = null) { if ($params === null) { - return; + return null; } if (in_array('verify-activitystream', $params, true)) { - $negotiate = Services::negotiator(); + $negotiate = service('negotiator'); $allowedContentTypes = [ 'application/ld+json; profile="https://www.w3.org/ns/activitystreams', @@ -44,10 +41,12 @@ class FediverseFilter implements FilterInterface } if (in_array('verify-blocks', $params, true)) { + // @phpstan-ignore-next-line $payload = $request->getJSON(); $actorUri = $payload->actor; - $domain = (new URI($actorUri))->getHost(); + $domain = new URI($actorUri) + ->getHost(); // check first if domain is blocked if (model('BlockedDomainModel', false)->isDomainBlocked($domain)) { @@ -60,29 +59,31 @@ class FediverseFilter implements FilterInterface } } + log_message('critical', 'ITS HEEEEEEEEEEEERE'); + if (in_array('verify-signature', $params, true)) { try { // securityCheck: check activity signature before handling it - (new HttpSignature())->verify(); + new HttpSignature() + ->verify(); } catch (Exception) { // Invalid HttpSignature (401 = unauthorized) // TODO: show error message? return service('response')->setStatusCode(401); } } - } - //-------------------------------------------------------------------- + return null; + } /** - * Allows After filters to inspect and modify the response object as needed. This method does not allow any way to - * stop execution of other after filters, short of throwing an Exception or Error. + * @param string[]|null $arguments * - * @param string[]|null $arguments + * @return ResponseInterface|null */ - public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void + #[Override] + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { + return null; } - - //-------------------------------------------------------------------- } diff --git a/modules/Fediverse/Helpers/fediverse_helper.php b/modules/Fediverse/Helpers/fediverse_helper.php index f4500585..391842a8 100644 --- a/modules/Fediverse/Helpers/fediverse_helper.php +++ b/modules/Fediverse/Helpers/fediverse_helper.php @@ -10,10 +10,11 @@ declare(strict_types=1); use CodeIgniter\HTTP\Exceptions\HTTPException; use CodeIgniter\HTTP\URI; -use Config\Database; -use Essence\Essence; +use Config\Mimes; +use Embera\Embera; use Modules\Fediverse\Activities\AcceptActivity; use Modules\Fediverse\ActivityRequest; +use Modules\Fediverse\Core\ObjectType; use Modules\Fediverse\Entities\Actor; use Modules\Fediverse\Entities\PreviewCard; @@ -32,7 +33,7 @@ if (! function_exists('get_webfinger_data')) { $webfingerRequest = new ActivityRequest((string) $webfingerUri); $webfingerResponse = $webfingerRequest->get(); - return json_decode($webfingerResponse->getBody(), false, 512, JSON_THROW_ON_ERROR); + return json_decode((string) $webfingerResponse->getBody(), false, 512, JSON_THROW_ON_ERROR); } } @@ -40,7 +41,7 @@ if (! function_exists('split_handle')) { /** * Splits handle into its parts (username, host and port) * - * @return array|false + * @return array{0:string,username:non-empty-string,1:non-empty-string,domain:non-empty-string,2:non-empty-string,port?:non-falsy-string,3?:non-falsy-string} */ function split_handle(string $handle): array | false { @@ -64,8 +65,15 @@ if (! function_exists('accept_follow')) { function accept_follow(Actor $actor, Actor $targetActor, string $objectId): void { $acceptActivity = new AcceptActivity(); + + $object = new ObjectType(); + $object->set('id', $objectId); + $object->set('type', 'Follow'); + $object->set('actor', $targetActor->uri); + $object->set('object', $actor->uri); + $acceptActivity->set('actor', $actor->uri) - ->set('object', $objectId); + ->set('object', $object); $db = db_connect(); $db->transStart(); @@ -123,6 +131,7 @@ if (! function_exists('send_activity_to_followers')) { */ function send_activity_to_followers(Actor $actor, string $activityPayload): void { + // TODO: send activities in parallel with https://www.php.net/manual/en/function.curl-multi-init.php foreach ($actor->followers as $follower) { send_activity_to_actor($actor, $follower, $activityPayload); } @@ -149,38 +158,33 @@ if (! function_exists('create_preview_card_from_url')) { */ function create_preview_card_from_url(URI $url): ?PreviewCard { - $essence = new Essence([ - 'filters' => [ - 'OEmbedProvider' => '//', - 'OpenGraphProvider' => '//', - 'TwitterCardsProvider' => '//', - ], - ]); - $media = $essence->extract((string) $url); + $embera = new Embera(); + $mediaData = $embera->getUrlData((string) $url); - if ($media) { - $typeMapping = [ - 'photo' => 'image', - 'video' => 'video', - 'website' => 'link', - 'rich' => 'rich', - ]; + if ($mediaData !== []) { + $mediaUrl = array_key_first($mediaData); + $media = array_first($mediaData); - // Check that, at least, the url and title are set - if ($media->url && $media->title) { + if (array_key_exists('title', $media)) { + $typeMapping = [ + 'photo' => 'image', + 'video' => 'video', + 'website' => 'link', + 'rich' => 'rich', + ]; + + // Check that, at least, the url and title are set $newPreviewCard = new PreviewCard([ - 'url' => (string) $url, - 'title' => $media->title, - 'description' => $media->description, - 'type' => isset($typeMapping[$media->type]) - ? $typeMapping[$media->type] - : 'link', - 'author_name' => $media->authorName, - 'author_url' => $media->authorUrl, - 'provider_name' => $media->providerName, - 'provider_url' => $media->providerUrl, - 'image' => $media->thumbnailUrl, - 'html' => $media->html, + 'url' => $mediaUrl, + 'title' => $media['title'] ?? '', + 'description' => $media['description'] ?? '', + 'type' => $typeMapping[$media['type']] ?? 'link', + 'author_name' => $media['author_name'] ?? null, + 'author_url' => $media['author_url'] ?? null, + 'provider_name' => $media['provider_name'] ?? '', + 'provider_url' => $media['provider_url'] ?? '', + 'image' => $media['thumbnail_url'] ?? '', + 'html' => $media['html'] ?? '', ]); if ( @@ -206,8 +210,8 @@ if (! function_exists('get_or_create_preview_card_from_url')) { { // check if preview card has already been generated if ( - $previewCard = model('PreviewCardModel', false) - ->getPreviewCardFromUrl((string) $url) + ($previewCard = model('PreviewCardModel', false) + ->getPreviewCardFromUrl((string) $url)) instanceof PreviewCard ) { return $previewCard; } @@ -225,7 +229,7 @@ if (! function_exists('get_or_create_actor_from_uri')) { function get_or_create_actor_from_uri(string $actorUri): ?Actor { // check if actor exists in database already and return it - if ($actor = model('ActorModel', false)->getActorByUri($actorUri)) { + if (($actor = model('ActorModel', false)->getActorByUri($actorUri)) instanceof Actor) { return $actor; } @@ -243,8 +247,8 @@ if (! function_exists('get_or_create_actor')) { { // check if actor exists in database already and return it if ( - $actor = model('ActorModel', false) - ->getActorByUsername($username, $domain) + ($actor = model('ActorModel', false) + ->getActorByUsername($username, $domain)) instanceof Actor ) { return $actor; } @@ -265,7 +269,7 @@ if (! function_exists('create_actor_from_uri')) { { $activityRequest = new ActivityRequest($actorUri); $actorResponse = $activityRequest->get(); - $actorPayload = json_decode($actorResponse->getBody(), false, 512, JSON_THROW_ON_ERROR); + $actorPayload = json_decode((string) $actorResponse->getBody(), false, 512, JSON_THROW_ON_ERROR); $newActor = new Actor(); $newActor->uri = $actorUri; @@ -277,12 +281,26 @@ if (! function_exists('create_actor_from_uri')) { $newActor->summary = property_exists($actorPayload, 'summary') ? $actorPayload->summary : null; if (property_exists($actorPayload, 'icon')) { $newActor->avatar_image_url = $actorPayload->icon->url; - $newActor->avatar_image_mimetype = $actorPayload->icon->mediaType; + + if (property_exists($actorPayload->icon, 'mediaType')) { + $newActor->avatar_image_mimetype = $actorPayload->icon->mediaType; + } else { + $iconExtension = pathinfo((string) $actorPayload->icon->url, PATHINFO_EXTENSION); + + $newActor->avatar_image_mimetype = (string) Mimes::guessTypeFromExtension($iconExtension); + } } if (property_exists($actorPayload, 'image')) { $newActor->cover_image_url = $actorPayload->image->url; - $newActor->cover_image_mimetype = $actorPayload->image->mediaType; + + if (property_exists($actorPayload->image, 'mediaType')) { + $newActor->cover_image_mimetype = $actorPayload->image->mediaType; + } else { + $coverExtension = pathinfo((string) $actorPayload->image->url, PATHINFO_EXTENSION); + + $newActor->cover_image_mimetype = (string) Mimes::guessTypeFromExtension($coverExtension); + } } $newActor->inbox_url = $actorPayload->inbox; @@ -327,7 +345,7 @@ if (! function_exists('get_message_from_object')) { */ function get_message_from_object(stdClass $object): string | false { - if (property_exists($object, 'content')) { + if (property_exists($object, 'content') && is_string($object->content)) { extract_text_from_html($object->content); return $object->content; } @@ -347,6 +365,29 @@ if (! function_exists('get_message_from_object')) { } } +if (! function_exists('is_note_public')) { + /** + * Check whether note is public or not + */ + function is_note_public(stdClass $object): bool + { + $isPublic = false; + if (property_exists($object, 'to') && is_array($object->to)) { + $isPublic = in_array('https://www.w3.org/ns/activitystreams#Public', $object->to, true); + } + + if ($isPublic) { + return true; + } + + if (property_exists($object, 'cc') && is_array($object->cc)) { + return in_array('https://www.w3.org/ns/activitystreams#Public', $object->cc, true); + } + + return $isPublic; + } +} + if (! function_exists('linkify')) { /** * Turn all link elements in clickable links. Transforms urls and handles @@ -362,7 +403,7 @@ if (! function_exists('linkify')) { $text = match ($protocol) { 'http', 'https' => preg_replace_callback( '~(?:(https?)://([^\s<]+)|(www\.[^\s<]+?\.[^\s<]+))(? '_blank', - 'rel' => 'noopener noreferrer', + 'rel' => 'noopener noreferrer', ], ), ) . '>'; }, - $text, + (string) $text, ), 'handle' => preg_replace_callback( '~(?\w++)(?:@(?(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]))?~', - static function ($match) use (&$links) { + static function (array $match) use (&$links): string { // check if host is set and look for actor in database - if (isset($match['host'])) { + if (isset($match['domain'])) { if ( - $actor = model( + ($actor = model( 'ActorModel', - )->getActorByUsername($match['username'], $match['domain']) + )->getActorByUsername($match['username'], $match['domain'])) instanceof Actor ) { // TODO: check that host is local to remove target blank? return '<' . @@ -402,7 +443,7 @@ if (! function_exists('linkify')) { $links, anchor($actor->uri, $match[0], [ 'target' => '_blank', - 'rel' => 'noopener noreferrer', + 'rel' => 'noopener noreferrer', ]), ) . '>'; @@ -415,7 +456,7 @@ if (! function_exists('linkify')) { $links, anchor($actor->uri, $match[0], [ 'target' => '_blank', - 'rel' => 'noopener noreferrer', + 'rel' => 'noopener noreferrer', ]), ) . '>'; @@ -427,8 +468,8 @@ if (! function_exists('linkify')) { } } else { if ( - $actor = model('ActorModel', false) - ->getActorByUsername($match['username']) + ($actor = model('ActorModel', false) + ->getActorByUsername($match['username'])) instanceof Actor ) { return '<' . array_push($links, anchor($actor->uri, $match[0])) . @@ -440,13 +481,13 @@ if (! function_exists('linkify')) { '>'; } }, - $text, + (string) $text, ), default => preg_replace_callback( '~' . preg_quote($protocol, '~') . '://([^\s<]+?)(? '_blank', - 'rel' => 'noopener noreferrer', + 'rel' => 'noopener noreferrer', ], ), ) . '>'; }, - $text, + (string) $text, ), }; } @@ -469,10 +510,10 @@ if (! function_exists('linkify')) { // Insert all links return preg_replace_callback( '~<(\d+)>~', - static function ($match) use (&$links) { - return $links[$match[1] - 1]; + static function (array $match) use (&$links): string { + return $links[(int) $match[1] - 1]; }, - $text, + (string) $text, ); } } diff --git a/modules/Fediverse/HttpSignature.php b/modules/Fediverse/HttpSignature.php index f5d08e43..145f0848 100644 --- a/modules/Fediverse/HttpSignature.php +++ b/modules/Fediverse/HttpSignature.php @@ -16,7 +16,6 @@ namespace Modules\Fediverse; use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\I18n\Time; -use Config\Services; use Exception; use phpseclib\Crypt\RSA; @@ -25,10 +24,7 @@ use phpseclib\Crypt\RSA; */ class HttpSignature { - /** - * @var string - */ - private const SIGNATURE_PATTERN = '/ + private const string SIGNATURE_PATTERN = '/ (?=.*(keyId="(?Phttps?:\/\/[\w\-\.]+[\w]+(:[\d]+)?[\w\-\.#\/@]+)")) (?=.*(signature="(?P[\w+\/]+={0,2})")) (?=.*(headers="\(request-target\)(?P[\w\\-\s]+)"))? @@ -37,10 +33,10 @@ class HttpSignature protected IncomingRequest $request; - public function __construct(IncomingRequest $request = null) + public function __construct(?IncomingRequest $request = null) { - if ($request === null) { - $request = Services::request(); + if (! $request instanceof IncomingRequest) { + $request = service('request'); } $this->request = $request; @@ -73,7 +69,7 @@ class HttpSignature } // compute body digest and compare with header digest - $bodyDigest = hash('sha256', $this->request->getBody(), true); + $bodyDigest = hash('sha256', (string) $this->request->getBody(), true); $digest = 'SHA-256=' . base64_encode($bodyDigest); if ($digest !== $digestHeader->getValue()) { throw new Exception('Request digest is incorrect.'); @@ -99,7 +95,7 @@ class HttpSignature // Fetch the public key linked from keyId $actorRequest = new ActivityRequest($keyId); $actorResponse = $actorRequest->get(); - $actor = json_decode($actorResponse->getBody(), false, 512, JSON_THROW_ON_ERROR); + $actor = json_decode((string) $actorResponse->getBody(), false, 512, JSON_THROW_ON_ERROR); $publicKeyPem = (string) $actor->publicKey->publicKeyPem; @@ -116,7 +112,7 @@ class HttpSignature * * @return array|false */ - private function splitSignature(string $signature): array | false + private function splitSignature(string $signature): bool|array { if (! preg_match(self::SIGNATURE_PATTERN, $signature, $matches, PREG_UNMATCHED_AS_NULL)) { // Signature pattern failed @@ -142,9 +138,10 @@ class HttpSignature $strings[] = sprintf( '(request-target): %s %s%s', $this->request->getMethod(), - '/' . $this->request->uri->getPath(), - $this->request->uri->getQuery() !== '' - ? '?' . $this->request->uri->getQuery() + '/' . $this->request->getUri()->getPath(), + $this->request->getUri() + ->getQuery() !== '' + ? '?' . $this->request->getUri()->getQuery() : '', ); @@ -164,7 +161,7 @@ class HttpSignature string $publicKeyPem, string $data, string $signature, - string $algorithm = 'rsa-sha256' + string $algorithm = 'rsa-sha256', ): bool { if ($algorithm === 'rsa-sha512' || $algorithm === 'rsa-sha256') { $hash = substr($algorithm, strpos($algorithm, '-') + 1); diff --git a/modules/Fediverse/Models/ActivityModel.php b/modules/Fediverse/Models/ActivityModel.php index dcb1f0ad..21cd5538 100644 --- a/modules/Fediverse/Models/ActivityModel.php +++ b/modules/Fediverse/Models/ActivityModel.php @@ -13,14 +13,15 @@ namespace Modules\Fediverse\Models; use CodeIgniter\Database\BaseResult; use CodeIgniter\I18n\Time; use DateTimeInterface; +use Michalsn\Uuid\UuidModel; use Modules\Fediverse\Entities\Activity; -class ActivityModel extends BaseUuidModel +class ActivityModel extends UuidModel { /** * @var string */ - protected $table = 'activities'; + protected $table = 'fediverse_activities'; /** * @var string @@ -33,17 +34,17 @@ class ActivityModel extends BaseUuidModel protected $uuidFields = ['id', 'post_id']; /** - * @var string[] + * @var list */ protected $afterInsert = ['notify']; /** - * @var string[] + * @var list */ protected $afterUpdate = ['notify']; /** - * @var string[] + * @var list */ protected $allowedFields = [ 'id', @@ -57,7 +58,7 @@ class ActivityModel extends BaseUuidModel ]; /** - * @var string + * @var class-string */ protected $returnType = Activity::class; @@ -71,7 +72,7 @@ class ActivityModel extends BaseUuidModel */ protected $useTimestamps = true; - protected $updatedField; + protected $updatedField = ''; public function getActivityById(string $activityId): ?Activity { @@ -99,18 +100,21 @@ class ActivityModel extends BaseUuidModel ?int $targetActorId, ?string $postId, string $payload, - DateTimeInterface $scheduledAt = null, - ?string $taskStatus = null + ?DateTimeInterface $scheduledAt = null, + ?string $taskStatus = null, ): BaseResult | int | string | false { return $this->insert( [ - 'actor_id' => $actorId, + 'actor_id' => $actorId, 'target_actor_id' => $targetActorId, - 'post_id' => $postId, - 'type' => $type === 'Undo' ? $type . '_' . (json_decode($payload, true))['object']['type'] : $type, - 'payload' => $payload, + 'post_id' => $postId, + 'type' => $type === 'Undo' ? $type . '_' . (json_decode( + $payload, + true, + ))['object']['type'] : $type, + 'payload' => $payload, 'scheduled_at' => $scheduledAt, - 'status' => $taskStatus, + 'status' => $taskStatus, ], true, ); @@ -119,21 +123,24 @@ class ActivityModel extends BaseUuidModel /** * @return Activity[] */ - public function getScheduledActivities(): array + public function getScheduledActivities(int $limit = 10): array { return $this->where('`scheduled_at` <= UTC_TIMESTAMP()', null, false) ->where('status', 'queued') ->orderBy('scheduled_at', 'ASC') + ->limit($limit) ->findAll(); } /** - * @param array> $data + * @param array $data * @return array> */ protected function notify(array $data): array { - $activity = (new self())->getActivityById(is_array($data['id']) ? $data['id'][0] : $data['id']); + /** @var ?Activity $activity */ + $activity = new self() + ->find(is_array($data['id']) ? $data['id'][0] : $data['id']); if (! $activity instanceof Activity) { return $data; @@ -149,43 +156,47 @@ class ActivityModel extends BaseUuidModel } if ($activity->type === 'Follow') { - (new NotificationModel())->insert([ - 'actor_id' => $activity->actor_id, - 'target_actor_id' => $activity->target_actor_id, - 'activity_id' => $activity->id, - 'type' => 'follow', - 'created_at' => $activity->created_at, - ]); - } elseif ($activity->type === 'Undo_Follow') { - (new NotificationModel())->builder() - ->delete([ - 'actor_id' => $activity->actor_id, + new NotificationModel() + ->insert([ + 'actor_id' => $activity->actor_id, 'target_actor_id' => $activity->target_actor_id, - 'type' => 'follow', + 'activity_id' => $activity->id, + 'type' => 'follow', + 'created_at' => $activity->created_at, + ]); + } elseif ($activity->type === 'Undo_Follow') { + new NotificationModel() + ->builder() + ->delete([ + 'actor_id' => $activity->actor_id, + 'target_actor_id' => $activity->target_actor_id, + 'type' => 'follow', ]); } elseif (in_array($activity->type, ['Create', 'Like', 'Announce'], true) && $activity->post_id !== null) { - (new NotificationModel())->insert([ - 'actor_id' => $activity->actor_id, - 'target_actor_id' => $activity->target_actor_id, - 'post_id' => $activity->post_id, - 'activity_id' => $activity->id, - 'type' => match ($activity->type) { - 'Create' => 'reply', - 'Like' => 'like', - 'Announce' => 'share', - }, - 'created_at' => $activity->created_at, - ]); - } elseif (in_array($activity->type, ['Undo_Like', 'Undo_Announce'], true) && $activity->post_id !== null) { - (new NotificationModel())->builder() - ->delete([ - 'actor_id' => $activity->actor_id, + new NotificationModel() + ->insert([ + 'actor_id' => $activity->actor_id, 'target_actor_id' => $activity->target_actor_id, - 'post_id' => service('uuid') + 'post_id' => $activity->post_id, + 'activity_id' => $activity->id, + 'type' => match ($activity->type) { + 'Create' => 'reply', + 'Like' => 'like', + 'Announce' => 'share', + }, + 'created_at' => $activity->created_at, + ]); + } elseif (in_array($activity->type, ['Undo_Like', 'Undo_Announce'], true) && $activity->post_id !== null) { + new NotificationModel() + ->builder() + ->delete([ + 'actor_id' => $activity->actor_id, + 'target_actor_id' => $activity->target_actor_id, + 'post_id' => service('uuid') ->fromString($activity->post_id) ->getBytes(), 'type' => match ($activity->type) { - 'Undo_Like' => 'like', + 'Undo_Like' => 'like', 'Undo_Announce' => 'share', }, ]); diff --git a/modules/Fediverse/Models/ActorModel.php b/modules/Fediverse/Models/ActorModel.php index 3c550ddc..c061e11d 100644 --- a/modules/Fediverse/Models/ActorModel.php +++ b/modules/Fediverse/Models/ActorModel.php @@ -11,17 +11,18 @@ declare(strict_types=1); namespace Modules\Fediverse\Models; use CodeIgniter\Events\Events; +use CodeIgniter\Model; use Modules\Fediverse\Entities\Actor; -class ActorModel extends BaseModel +class ActorModel extends Model { /** * @var string */ - protected $table = 'actors'; + protected $table = 'fediverse_actors'; /** - * @var string[] + * @var list */ protected $allowedFields = [ 'id', @@ -45,7 +46,7 @@ class ActorModel extends BaseModel ]; /** - * @var string + * @var class-string */ protected $returnType = Actor::class; @@ -61,16 +62,7 @@ class ActorModel extends BaseModel public function getActorById(int $id): ?Actor { - $cacheName = config('Fediverse') - ->cachePrefix . "actor#{$id}"; - if (! ($found = cache($cacheName))) { - $found = $this->find($id); - - cache() - ->save($cacheName, $found, DECADE); - } - - return $found; + return $this->find($id); } /** @@ -92,7 +84,7 @@ class ActorModel extends BaseModel if (! ($found = cache($cacheName))) { $found = $this->where([ 'username' => $username, - 'domain' => $domain, + 'domain' => $domain, ])->first(); cache() @@ -128,10 +120,8 @@ class ActorModel extends BaseModel config('Fediverse') ->cachePrefix . "actor#{$actorId}_followers"; if (! ($found = cache($cacheName))) { - $tablesPrefix = config('Fediverse') - ->tablesPrefix; - $found = $this->join($tablesPrefix . 'follows', $tablesPrefix . 'follows.actor_id = id', 'inner') - ->where($tablesPrefix . 'follows.target_actor_id', $actorId) + $found = $this->join('fediverse_follows', 'fediverse_follows.actor_id = id', 'inner') + ->where('fediverse_follows.target_actor_id', $actorId) ->findAll(); cache() @@ -146,7 +136,7 @@ class ActorModel extends BaseModel */ public function isActorBlocked(string $actorUri): bool { - if (($actor = $this->getActorByUri($actorUri)) !== null) { + if (($actor = $this->getActorByUri($actorUri)) instanceof Actor) { return $actor->is_blocked; } @@ -212,7 +202,8 @@ class ActorModel extends BaseModel $cacheName = config('Fediverse') ->cachePrefix . 'blocked_actors'; if (! ($found = cache($cacheName))) { - $result = $this->select('COUNT(*) as total_local_actors') + $result = $this->builder() + ->select('COUNT(*) as total_local_actors') ->where('domain', get_current_domain()) ->get() ->getResultArray(); @@ -234,30 +225,30 @@ class ActorModel extends BaseModel ->cachePrefix . 'blocked_actors'; if (! ($found = cache($cacheName))) { $tablePrefix = config('Database') - ->default['DBPrefix'] . config('Fediverse') - ->tablesPrefix; - $result = $this->select('COUNT(DISTINCT `cp_fediverse_actors`.`id`) as `total_active_actors`', false) + ->default['DBPrefix']; + $result = $this->builder() + ->select('COUNT(DISTINCT `' . $tablePrefix . 'fediverse_actors`.`id`) as `total_active_actors`', false) ->join( - $tablePrefix . 'posts', - $tablePrefix . 'actors.id = ' . $tablePrefix . 'posts.actor_id', - 'left outer' + $tablePrefix . 'fediverse_posts', + $tablePrefix . 'fediverse_actors.id = ' . $tablePrefix . 'fediverse_posts.actor_id', + 'left outer', ) ->join( - $tablePrefix . 'favourites', - $tablePrefix . 'actors.id = ' . $tablePrefix . 'favourites.actor_id', - 'left outer' + $tablePrefix . 'fediverse_favourites', + $tablePrefix . 'fediverse_actors.id = ' . $tablePrefix . 'fediverse_favourites.actor_id', + 'left outer', ) - ->where($tablePrefix . 'actors.domain', get_current_domain()) + ->where($tablePrefix . 'fediverse_actors.domain', get_current_domain()) ->groupStart() ->where( - "`{$tablePrefix}posts`.`created_at` >= UTC_TIMESTAMP() - INTERVAL {$lastNumberOfMonths} month", + "`{$tablePrefix}fediverse_posts`.`created_at` >= UTC_TIMESTAMP() - INTERVAL {$lastNumberOfMonths} month", null, - false + false, ) ->orWhere( - "`{$tablePrefix}favourites`.`created_at` >= UTC_TIMESTAMP() - INTERVAL {$lastNumberOfMonths} month", + "`{$tablePrefix}fediverse_favourites`.`created_at` >= UTC_TIMESTAMP() - INTERVAL {$lastNumberOfMonths} month", null, - false + false, ) ->groupEnd() ->get() @@ -274,12 +265,8 @@ class ActorModel extends BaseModel public function resetFollowersCount(): int | false { - $tablePrefix = config('Fediverse') - ->tablesPrefix; - - $actorsFollowersCount = $this->db->table($tablePrefix . 'follows')->select( - 'target_actor_id as id, COUNT(*) as `followers_count`' - ) + $actorsFollowersCount = $this->db->table('fediverse_follows') + ->select('target_actor_id as id, COUNT(*) as `followers_count`') ->groupBy('id') ->get() ->getResultArray(); @@ -293,12 +280,8 @@ class ActorModel extends BaseModel public function resetPostsCount(): int | false { - $tablePrefix = config('Fediverse') - ->tablesPrefix; - - $actorsFollowersCount = $this->db->table($tablePrefix . 'posts')->select( - 'actor_id as id, COUNT(*) as `posts_count`' - ) + $actorsFollowersCount = $this->db->table('fediverse_posts') + ->select('actor_id as id, COUNT(*) as `posts_count`') ->where([ 'in_reply_to_id' => null, ]) diff --git a/modules/Fediverse/Models/BaseModel.php b/modules/Fediverse/Models/BaseModel.php deleted file mode 100644 index 685f1776..00000000 --- a/modules/Fediverse/Models/BaseModel.php +++ /dev/null @@ -1,26 +0,0 @@ -table = config('Fediverse') - ->tablesPrefix . $this->table; - } -} diff --git a/modules/Fediverse/Models/BaseUuidModel.php b/modules/Fediverse/Models/BaseUuidModel.php deleted file mode 100644 index e2f80ea9..00000000 --- a/modules/Fediverse/Models/BaseUuidModel.php +++ /dev/null @@ -1,20 +0,0 @@ -table = config('Fediverse') - ->tablesPrefix . $this->table; - } -} diff --git a/modules/Fediverse/Models/BlockedDomainModel.php b/modules/Fediverse/Models/BlockedDomainModel.php index 933f6e52..3b8acf3f 100644 --- a/modules/Fediverse/Models/BlockedDomainModel.php +++ b/modules/Fediverse/Models/BlockedDomainModel.php @@ -12,14 +12,15 @@ namespace Modules\Fediverse\Models; use CodeIgniter\Database\BaseResult; use CodeIgniter\Events\Events; +use CodeIgniter\Model; use Modules\Fediverse\Entities\BlockedDomain; -class BlockedDomainModel extends BaseModel +class BlockedDomainModel extends Model { /** * @var string */ - protected $table = 'blocked_domains'; + protected $table = 'fediverse_blocked_domains'; /** * @var string @@ -27,12 +28,12 @@ class BlockedDomainModel extends BaseModel protected $primaryKey = 'name'; /** - * @var string[] + * @var list */ protected $allowedFields = ['name']; /** - * @var string + * @var class-string */ protected $returnType = BlockedDomain::class; @@ -46,7 +47,7 @@ class BlockedDomainModel extends BaseModel */ protected $useTimestamps = true; - protected $updatedField; + protected $updatedField = ''; /** * Retrieves instance or podcast domain blocks depending on whether or not $podcastId param is set. diff --git a/modules/Fediverse/Models/FavouriteModel.php b/modules/Fediverse/Models/FavouriteModel.php index 3ca5aa62..0c1dfc6f 100644 --- a/modules/Fediverse/Models/FavouriteModel.php +++ b/modules/Fediverse/Models/FavouriteModel.php @@ -11,18 +11,19 @@ declare(strict_types=1); namespace Modules\Fediverse\Models; use CodeIgniter\Events\Events; +use Michalsn\Uuid\UuidModel; use Modules\Fediverse\Activities\LikeActivity; use Modules\Fediverse\Activities\UndoActivity; use Modules\Fediverse\Entities\Actor; use Modules\Fediverse\Entities\Favourite; use Modules\Fediverse\Entities\Post; -class FavouriteModel extends BaseUuidModel +class FavouriteModel extends UuidModel { /** * @var string */ - protected $table = 'favourites'; + protected $table = 'fediverse_favourites'; /** * @var string[] @@ -30,12 +31,12 @@ class FavouriteModel extends BaseUuidModel protected $uuidFields = ['post_id']; /** - * @var string[] + * @var list */ protected $allowedFields = ['actor_id', 'post_id']; /** - * @var string + * @var class-string */ protected $returnType = Favourite::class; @@ -44,7 +45,7 @@ class FavouriteModel extends BaseUuidModel */ protected $useTimestamps = true; - protected $updatedField; + protected $updatedField = ''; public function addFavourite(Actor $actor, Post $post, bool $registerActivity = true): void { @@ -52,10 +53,11 @@ class FavouriteModel extends BaseUuidModel $this->insert([ 'actor_id' => $actor->id, - 'post_id' => $post->id, + 'post_id' => $post->id, ]); model('PostModel', false) + ->builder() ->where('id', service('uuid') ->fromString($post->id) ->getBytes()) ->increment('favourites_count'); @@ -96,12 +98,13 @@ class FavouriteModel extends BaseUuidModel $this->db->transStart(); model('PostModel', false) + ->builder() ->where('id', service('uuid') ->fromString($post->id) ->getBytes()) ->decrement('favourites_count'); $this->where([ 'actor_id' => $actor->id, - 'post_id' => service('uuid') + 'post_id' => service('uuid') ->fromString($post->id) ->getBytes(), ]) @@ -112,9 +115,9 @@ class FavouriteModel extends BaseUuidModel // get like activity $activity = model('ActivityModel', false) ->where([ - 'type' => 'Like', + 'type' => 'Like', 'actor_id' => $actor->id, - 'post_id' => service('uuid') + 'post_id' => service('uuid') ->fromString($post->id) ->getBytes(), ]) @@ -165,10 +168,10 @@ class FavouriteModel extends BaseUuidModel if ( $this->where([ 'actor_id' => $actor->id, - 'post_id' => service('uuid') + 'post_id' => service('uuid') ->fromString($post->id) ->getBytes(), - ])->first() + ])->first() instanceof Favourite ) { $this->removeFavourite($actor, $post); } else { diff --git a/modules/Fediverse/Models/FollowModel.php b/modules/Fediverse/Models/FollowModel.php index 5a49b0a8..9541625a 100644 --- a/modules/Fediverse/Models/FollowModel.php +++ b/modules/Fediverse/Models/FollowModel.php @@ -12,26 +12,27 @@ namespace Modules\Fediverse\Models; use CodeIgniter\Events\Events; use CodeIgniter\I18n\Time; +use CodeIgniter\Model; use Exception; use Modules\Fediverse\Activities\FollowActivity; use Modules\Fediverse\Activities\UndoActivity; use Modules\Fediverse\Entities\Actor; use Modules\Fediverse\Entities\Follow; -class FollowModel extends BaseModel +class FollowModel extends Model { /** * @var string */ - protected $table = 'follows'; + protected $table = 'fediverse_follows'; /** - * @var string[] + * @var list */ protected $allowedFields = ['actor_id', 'target_actor_id']; /** - * @var string + * @var class-string */ protected $returnType = Follow::class; @@ -40,7 +41,7 @@ class FollowModel extends BaseModel */ protected $useTimestamps = true; - protected $updatedField; + protected $updatedField = ''; /** * @param Actor $actor Actor that is following @@ -52,12 +53,13 @@ class FollowModel extends BaseModel $this->db->transStart(); $this->insert([ - 'actor_id' => $actor->id, + 'actor_id' => $actor->id, 'target_actor_id' => $targetActor->id, ]); // increment followers_count for target actor model('ActorModel', false) + ->builder() ->where('id', $targetActor->id) ->increment('followers_count'); @@ -107,12 +109,13 @@ class FollowModel extends BaseModel $this->db->transStart(); $this->where([ - 'actor_id' => $actor->id, + 'actor_id' => $actor->id, 'target_actor_id' => $targetActor->id, ])->delete(); // decrement followers_count for target actor model('ActorModel', false) + ->builder() ->where('id', $targetActor->id) ->decrement('followers_count'); @@ -121,8 +124,8 @@ class FollowModel extends BaseModel // get follow activity from database $followActivity = model('ActivityModel', false) ->where([ - 'type' => 'Follow', - 'actor_id' => $actor->id, + 'type' => 'Follow', + 'actor_id' => $actor->id, 'target_actor_id' => $targetActor->id, ]) ->first(); diff --git a/modules/Fediverse/Models/NotificationModel.php b/modules/Fediverse/Models/NotificationModel.php index 24732d7d..b0993d6e 100644 --- a/modules/Fediverse/Models/NotificationModel.php +++ b/modules/Fediverse/Models/NotificationModel.php @@ -10,14 +10,15 @@ declare(strict_types=1); namespace Modules\Fediverse\Models; +use Michalsn\Uuid\UuidModel; use Modules\Fediverse\Entities\Notification; -class NotificationModel extends BaseUuidModel +class NotificationModel extends UuidModel { /** * @var string */ - protected $table = 'notifications'; + protected $table = 'fediverse_notifications'; /** * @var string @@ -25,7 +26,7 @@ class NotificationModel extends BaseUuidModel protected $primaryKey = 'id'; /** - * @var string + * @var class-string */ protected $returnType = Notification::class; @@ -40,7 +41,7 @@ class NotificationModel extends BaseUuidModel protected $uuidFields = ['post_id', 'activity_id']; /** - * @var string[] + * @var list */ protected $allowedFields = [ 'actor_id', diff --git a/modules/Fediverse/Models/PostModel.php b/modules/Fediverse/Models/PostModel.php index 94f2130c..e24000cc 100644 --- a/modules/Fediverse/Models/PostModel.php +++ b/modules/Fediverse/Models/PostModel.php @@ -11,11 +11,11 @@ declare(strict_types=1); namespace Modules\Fediverse\Models; use CodeIgniter\Database\BaseResult; -use CodeIgniter\Database\Query; use CodeIgniter\Events\Events; use CodeIgniter\HTTP\URI; use CodeIgniter\I18n\Time; use Exception; +use Michalsn\Uuid\UuidModel; use Modules\Fediverse\Activities\AnnounceActivity; use Modules\Fediverse\Activities\CreateActivity; use Modules\Fediverse\Activities\DeleteActivity; @@ -24,12 +24,12 @@ use Modules\Fediverse\Entities\Actor; use Modules\Fediverse\Entities\Post; use Modules\Fediverse\Objects\TombstoneObject; -class PostModel extends BaseUuidModel +class PostModel extends UuidModel { /** * @var string */ - protected $table = 'posts'; + protected $table = 'fediverse_posts'; /** * @var string @@ -42,7 +42,7 @@ class PostModel extends BaseUuidModel protected $uuidFields = ['id', 'in_reply_to_id', 'reblog_of_id']; /** - * @var string[] + * @var list */ protected $allowedFields = [ 'id', @@ -52,6 +52,7 @@ class PostModel extends BaseUuidModel 'reblog_of_id', 'message', 'message_html', + 'is_private', 'favourites_count', 'reblogs_count', 'replies_count', @@ -59,7 +60,7 @@ class PostModel extends BaseUuidModel ]; /** - * @var string + * @var class-string */ protected $returnType = Post::class; @@ -73,33 +74,24 @@ class PostModel extends BaseUuidModel */ protected $useTimestamps = true; - protected $updatedField; + protected $updatedField = ''; /** * @var array */ protected $validationRules = [ - 'actor_id' => 'required', + 'actor_id' => 'required', 'message_html' => 'max_length[500]', ]; /** - * @var string[] + * @var list */ protected $beforeInsert = ['setPostId']; public function getPostById(string $postId): ?Post { - $cacheName = config('Fediverse') - ->cachePrefix . "post#{$postId}"; - if (! ($found = cache($cacheName))) { - $found = $this->find($postId); - - cache() - ->save($cacheName, $found, DECADE); - } - - return $found; + return $this->find($postId); } public function getPostByUri(string $postUri): ?Post @@ -132,7 +124,7 @@ class PostModel extends BaseUuidModel "actor#{$actorId}_published_posts"; if (! ($found = cache($cacheName))) { $found = $this->where([ - 'actor_id' => $actorId, + 'actor_id' => $actorId, 'in_reply_to_id' => null, ]) ->where('`published_at` <= UTC_TIMESTAMP()', null, false) @@ -142,7 +134,7 @@ class PostModel extends BaseUuidModel $secondsToNextUnpublishedPost = $this->getSecondsToNextUnpublishedPosts($actorId); cache() - ->save($cacheName, $found, $secondsToNextUnpublishedPost ? $secondsToNextUnpublishedPost : DECADE); + ->save($cacheName, $found, $secondsToNextUnpublishedPost ?: DECADE); } return $found; @@ -154,7 +146,8 @@ class PostModel extends BaseUuidModel */ public function getSecondsToNextUnpublishedPosts(int $actorId): int | false { - $result = $this->select('TIMESTAMPDIFF(SECOND, UTC_TIMESTAMP(), `published_at`) as timestamp_diff') + $result = $this->builder() + ->select('TIMESTAMPDIFF(SECOND, UTC_TIMESTAMP(), `published_at`) as timestamp_diff') ->where([ 'actor_id' => $actorId, ]) @@ -182,21 +175,21 @@ class PostModel extends BaseUuidModel ($withBlocked ? '_withBlocked' : ''); if (! ($found = cache($cacheName))) { - $tablesPrefix = config('Fediverse') - ->tablesPrefix; if (! $withBlocked) { - $this->select($tablesPrefix . 'posts.*') - ->join( - $tablesPrefix . 'actors', - $tablesPrefix . 'actors.id = ' . $tablesPrefix . 'posts.actor_id', - 'inner' - ) - ->where($tablesPrefix . 'actors.is_blocked', 0); + $this->select('fediverse_posts.*') + ->join('fediverse_actors', 'fediverse_actors.id = fediverse_posts.actor_id', 'inner') + ->where('fediverse_actors.is_blocked', 0); } $this->where('in_reply_to_id', $this->uuid->fromString($postId) ->getBytes()) ->where('`published_at` <= UTC_TIMESTAMP()', null, false) ->orderBy('published_at', 'ASC'); + + // do not get private replies if public + if (! can_user_interact()) { + $this->where('is_private', false); + } + $found = $this->findAll(); cache() @@ -230,9 +223,9 @@ class PostModel extends BaseUuidModel return $found; } - public function addPreviewCard(string $postId, int $previewCardId): Query | bool + public function addPreviewCard(string $postId, int $previewCardId): bool { - return $this->db->table(config('Fediverse')->tablesPrefix . 'posts_preview_cards') + return $this->db->table('fediverse_posts_preview_cards') ->insert([ 'post_id' => $this->uuid->fromString($postId) ->getBytes(), @@ -248,8 +241,8 @@ class PostModel extends BaseUuidModel public function addPost( Post $post, bool $createPreviewCard = true, - bool $registerActivity = true - ): string | false { + bool $registerActivity = true, + ): bool|int|object|string { helper('fediverse'); $this->db->transStart(); @@ -279,6 +272,7 @@ class PostModel extends BaseUuidModel if ($post->in_reply_to_id === null) { // post is not a reply model('ActorModel', false) + ->builder() ->where('id', $post->actor_id) ->increment('posts_count'); @@ -297,6 +291,10 @@ class PostModel extends BaseUuidModel ->set('actor', $post->actor->uri) ->set('object', new $noteObjectClass($post)); + if ($post->in_reply_to_id !== null && $post->is_private) { + $createActivity->set('to', [$post->reply_to_post->actor->uri]); + } + $activityId = model('ActivityModel', false) ->newActivity( 'Create', @@ -330,7 +328,7 @@ class PostModel extends BaseUuidModel // update post create activity schedule in database $scheduledActivity = model('ActivityModel', false) ->where([ - 'type' => 'Create', + 'type' => 'Create', 'post_id' => $this->uuid ->fromString($updatedPost->id) ->getBytes(), @@ -340,9 +338,10 @@ class PostModel extends BaseUuidModel // update published date in payload $newPayload = $scheduledActivity->payload; $newPayload->object->published = $updatedPost->published_at->format(DATE_W3C); + model('ActivityModel', false) ->update($scheduledActivity->id, [ - 'payload' => json_encode($newPayload, JSON_THROW_ON_ERROR), + 'payload' => json_encode($newPayload, JSON_THROW_ON_ERROR), 'scheduled_at' => $updatedPost->published_at, ]); @@ -380,7 +379,7 @@ class PostModel extends BaseUuidModel if ( $post->preview_card && $this->db - ->table(config('Fediverse')->tablesPrefix . 'posts_preview_cards') + ->table('fediverse_posts_preview_cards') ->where('preview_card_id', $post->preview_card->id) ->countAll() <= 1 ) { @@ -416,15 +415,19 @@ class PostModel extends BaseUuidModel if ($post->in_reply_to_id === null && $post->reblog_of_id === null) { model('ActorModel', false) + ->builder() ->where('id', $post->actor_id) ->decrement('posts_count'); Events::trigger('on_post_remove', $post); } elseif ($post->in_reply_to_id !== null) { - // Post to remove is a reply - model('PostModel', false) - ->where('id', $this->uuid->fromString($post->in_reply_to_id) ->getBytes()) - ->decrement('replies_count'); + if (! $post->is_private) { + // Post to remove is a reply + model('PostModel', false) + ->builder() + ->where('id', $this->uuid->fromString($post->in_reply_to_id) ->getBytes()) + ->decrement('replies_count'); + } Events::trigger('on_reply_remove', $post); } @@ -442,7 +445,7 @@ class PostModel extends BaseUuidModel public function addReply( Post $reply, bool $createPreviewCard = true, - bool $registerActivity = true + bool $registerActivity = true, ): string | false { if (! $reply->in_reply_to_id) { throw new Exception('Passed post is not a reply!'); @@ -452,9 +455,12 @@ class PostModel extends BaseUuidModel $postId = $this->addPost($reply, $createPreviewCard, $registerActivity); - model('PostModel', false) - ->where('id', $this->uuid->fromString($reply->in_reply_to_id) ->getBytes()) - ->increment('replies_count'); + if (! $reply->is_private) { + model('PostModel', false) + ->builder() + ->where('id', $this->uuid->fromString($reply->in_reply_to_id) ->getBytes()) + ->increment('replies_count'); + } Events::trigger('on_post_reply', $reply); @@ -467,6 +473,11 @@ class PostModel extends BaseUuidModel public function reblog(Actor $actor, Post $post, bool $registerActivity = true): string | false { + // cannot reblog a private post + if ($post->is_private) { + return false; + } + $this->db->transStart(); $userId = null; @@ -475,20 +486,22 @@ class PostModel extends BaseUuidModel } $reblog = new Post([ - 'actor_id' => $actor->id, + 'actor_id' => $actor->id, 'reblog_of_id' => $post->id, 'published_at' => Time::now(), - 'created_by' => $userId, + 'created_by' => $userId, ]); // add reblog $reblogId = $this->insert($reblog); model('ActorModel', false) + ->builder() ->where('id', $actor->id) ->increment('posts_count'); model('PostModel', false) + ->builder() ->where('id', $this->uuid->fromString($post->id)->getBytes()) ->increment('reblogs_count'); @@ -528,10 +541,12 @@ class PostModel extends BaseUuidModel $this->db->transStart(); model('ActorModel', false) + ->builder() ->where('id', $reblogPost->actor_id) ->decrement('posts_count'); model('PostModel', false) + ->builder() ->where('id', $this->uuid->fromString($reblogPost->reblog_of_id) ->getBytes()) ->decrement('reblogs_count'); @@ -540,16 +555,16 @@ class PostModel extends BaseUuidModel // get like activity $activity = model('ActivityModel', false) ->where([ - 'type' => 'Announce', + 'type' => 'Announce', 'actor_id' => $reblogPost->actor_id, - 'post_id' => $this->uuid + 'post_id' => $this->uuid ->fromString($reblogPost->reblog_of_id) ->getBytes(), ]) ->first(); $announceActivity = new AnnounceActivity($reblogPost); - $announceActivity->set('id', url_to('activity', $reblogPost->actor->username, $activity->id),); + $announceActivity->set('id', url_to('activity', $reblogPost->actor->username, $activity->id)); $undoActivity ->set('actor', $reblogPost->actor->uri) @@ -590,11 +605,11 @@ class PostModel extends BaseUuidModel { if ( ! ($reblogPost = $this->where([ - 'actor_id' => $actor->id, + 'actor_id' => $actor->id, 'reblog_of_id' => $this->uuid ->fromString($post->id) ->getBytes(), - ])->first()) + ])->first()) instanceof Post ) { $this->reblog($actor, $post); } else { @@ -609,11 +624,10 @@ class PostModel extends BaseUuidModel $cacheName = config('Fediverse') ->cachePrefix . 'blocked_actors'; if (! ($found = cache($cacheName))) { - $tablePrefix = config('Fediverse') - ->tablesPrefix; - $result = $this->select('COUNT(*) as total_local_posts') - ->join($tablePrefix . 'actors', $tablePrefix . 'actors.id = ' . $tablePrefix . 'posts.actor_id') - ->where($tablePrefix . 'actors.domain', get_current_domain()) + $result = $this->builder() + ->select('COUNT(*) as total_local_posts') + ->join('fediverse_actors', 'fediverse_actors.id = fediverse_posts.actor_id') + ->where('fediverse_actors.domain', get_current_domain()) ->where('`published_at` <= UTC_TIMESTAMP()', null, false) ->get() ->getResultArray(); @@ -629,12 +643,8 @@ class PostModel extends BaseUuidModel public function resetFavouritesCount(): int | false { - $tablePrefix = config('Fediverse') - ->tablesPrefix; - - $postsFavouritesCount = $this->db->table($tablePrefix . 'favourites')->select( - 'post_id as id, COUNT(*) as `favourites_count`' - ) + $postsFavouritesCount = $this->db->table('fediverse_favourites') + ->select('post_id as id, COUNT(*) as `favourites_count`') ->groupBy('id') ->get() ->getResultArray(); @@ -649,12 +659,10 @@ class PostModel extends BaseUuidModel public function resetReblogsCount(): int | false { - $tablePrefix = config('Fediverse') - ->tablesPrefix; - - $postsReblogsCount = $this->select($tablePrefix . 'posts.id, COUNT(*) as `replies_count`') - ->join($tablePrefix . 'posts as p2', $tablePrefix . 'posts.id = p2.reblog_of_id') - ->groupBy($tablePrefix . 'posts.id') + $postsReblogsCount = $this->builder() + ->select('fediverse_posts.id, COUNT(*) as `replies_count`') + ->join('fediverse_posts as p2', 'fediverse_posts.id = p2.reblog_of_id') + ->groupBy('fediverse_posts.id') ->get() ->getResultArray(); @@ -668,12 +676,10 @@ class PostModel extends BaseUuidModel public function resetRepliesCount(): int | false { - $tablePrefix = config('Fediverse') - ->tablesPrefix; - - $postsRepliesCount = $this->select($tablePrefix . 'posts.id, COUNT(*) as `replies_count`') - ->join($tablePrefix . 'posts as p2', $tablePrefix . 'posts.id = p2.in_reply_to_id') - ->groupBy($tablePrefix . 'posts.id') + $postsRepliesCount = $this->builder() + ->select('fediverse_posts.id, COUNT(*) as `replies_count`') + ->join('fediverse_posts as p2', 'fediverse_posts.id = p2.in_reply_to_id') + ->groupBy('fediverse_posts.id') ->get() ->getResultArray(); diff --git a/modules/Fediverse/Models/PreviewCardModel.php b/modules/Fediverse/Models/PreviewCardModel.php index fb9c028e..b0e64c96 100644 --- a/modules/Fediverse/Models/PreviewCardModel.php +++ b/modules/Fediverse/Models/PreviewCardModel.php @@ -11,17 +11,18 @@ declare(strict_types=1); namespace Modules\Fediverse\Models; use CodeIgniter\Database\BaseResult; +use CodeIgniter\Model; use Modules\Fediverse\Entities\PreviewCard; -class PreviewCardModel extends BaseModel +class PreviewCardModel extends Model { /** * @var string */ - protected $table = 'preview_cards'; + protected $table = 'fediverse_preview_cards'; /** - * @var string[] + * @var list */ protected $allowedFields = [ 'id', @@ -38,7 +39,7 @@ class PreviewCardModel extends BaseModel ]; /** - * @var string + * @var class-string */ protected $returnType = PreviewCard::class; @@ -75,11 +76,9 @@ class PreviewCardModel extends BaseModel config('Fediverse') ->cachePrefix . "post#{$postId}_preview_card"; if (! ($found = cache($cacheName))) { - $tablesPrefix = config('Fediverse') - ->tablesPrefix; $found = $this->join( - $tablesPrefix . 'posts_preview_cards', - $tablesPrefix . 'posts_preview_cards.preview_card_id = id', + 'fediverse_posts_preview_cards', + 'fediverse_posts_preview_cards.preview_card_id = id', 'inner', ) ->where('post_id', service('uuid') ->fromString($postId) ->getBytes()) diff --git a/modules/Fediverse/Objects/ActorObject.php b/modules/Fediverse/Objects/ActorObject.php index 99f84f5e..d00ccb84 100644 --- a/modules/Fediverse/Objects/ActorObject.php +++ b/modules/Fediverse/Objects/ActorObject.php @@ -68,21 +68,21 @@ class ActorObject extends ObjectType $this->followers = $actor->followers_url; $this->image = [ - 'type' => 'Image', + 'type' => 'Image', 'mediaType' => $actor->cover_image_mimetype, - 'url' => $actor->cover_image_url, + 'url' => $actor->cover_image_url, ]; $this->icon = [ - 'type' => 'Image', + 'type' => 'Image', 'mediaType' => $actor->avatar_image_mimetype, - 'url' => $actor->avatar_image_url, + 'url' => $actor->avatar_image_url, ]; if ($actor->public_key !== null) { $this->publicKey = [ - 'id' => $actor->public_key_id, - 'owner' => $actor->uri, + 'id' => $actor->public_key_id, + 'owner' => $actor->uri, 'publicKeyPem' => $actor->public_key, ]; } diff --git a/modules/Fediverse/Objects/NoteObject.php b/modules/Fediverse/Objects/NoteObject.php index 561ce89b..73af27a3 100644 --- a/modules/Fediverse/Objects/NoteObject.php +++ b/modules/Fediverse/Objects/NoteObject.php @@ -39,13 +39,19 @@ class NoteObject extends ObjectType $this->attributedTo = $post->actor->uri; if ($post->in_reply_to_id !== null) { - $this->to[] = $post->reply_to_post->actor->uri; + if ($post->is_private) { + $this->to = [$post->reply_to_post->actor->uri]; + } else { + $this->to[] = $post->reply_to_post->actor->uri; + } $this->inReplyTo = $post->reply_to_post->uri; } $this->replies = url_to('post-replies', esc($post->actor->username), $post->id); - $this->cc = [$post->actor->followers_url]; + if (! $post->is_private) { + $this->cc = [$post->actor->followers_url]; + } } } diff --git a/modules/Fediverse/Objects/OrderedCollectionObject.php b/modules/Fediverse/Objects/OrderedCollectionObject.php index 8012d226..339ffb03 100644 --- a/modules/Fediverse/Objects/OrderedCollectionObject.php +++ b/modules/Fediverse/Objects/OrderedCollectionObject.php @@ -28,15 +28,15 @@ class OrderedCollectionObject extends ObjectType protected ?string $last = null; /** - * @param ObjectType[]|null $orderedItems + * @param ObjectType[]|list|null $orderedItems */ public function __construct( protected ?array $orderedItems = null, - ?Pager $pager = null + ?Pager $pager = null, ) { $this->id = current_url(); - if ($pager !== null) { + if ($pager instanceof Pager) { $totalItems = $pager->getTotal(); $this->totalItems = $totalItems; diff --git a/modules/Fediverse/WebFinger.php b/modules/Fediverse/WebFinger.php index 82e0472f..e07f5115 100644 --- a/modules/Fediverse/WebFinger.php +++ b/modules/Fediverse/WebFinger.php @@ -11,13 +11,11 @@ declare(strict_types=1); namespace Modules\Fediverse; use Exception; +use Modules\Fediverse\Entities\Actor; class WebFinger { - /** - * @var string - */ - private const RESOURCE_PATTERN = '/^acct:(?P([\w_]+))@(?P([\w\-\.]+[\w]+)(:[\d]+)?)$/x'; + private const string RESOURCE_PATTERN = '/^acct:(?P([\w_]+))@(?P([\w\-\.]+[\w]+)(:[\d]+)?)$/x'; protected string $username; @@ -38,7 +36,7 @@ class WebFinger protected array $links = []; public function __construct( - protected string $subject + protected string $subject, ) { // Split resource into its parts (username, domain) $parts = $this->splitResource($subject); @@ -62,20 +60,20 @@ class WebFinger } if ( - ! ($actor = model('ActorModel', false)->getActorByUsername($username, $domain)) + ! ($actor = model('ActorModel', false)->getActorByUsername($username, $domain)) instanceof Actor ) { throw new Exception('Could not find actor'); } - $this->aliases = [$actor->id]; + $this->aliases = [$actor->uri]; $this->links = [ [ - 'rel' => 'self', + 'rel' => 'self', 'type' => 'application/activity+json', 'href' => $actor->uri, ], [ - 'rel' => 'http://webfinger.net/rel/profile-page', + 'rel' => 'http://webfinger.net/rel/profile-page', 'type' => 'text/html', 'href' => $actor->uri, # TODO: should there be 2 values? @actorUsername @@ -93,16 +91,16 @@ class WebFinger return [ 'subject' => $this->subject, 'aliases' => $this->aliases, - 'links' => $this->links, + 'links' => $this->links, ]; } /** * Split resource into its parts (username, domain) * - * @return array|false + * @return array{0:string,username:non-empty-string,1:non-empty-string,2:non-empty-string,domain:non-falsy-string,3:non-falsy-string,4:non-falsy-string,5?:non-falsy-string} */ - private function splitResource(string $resource): array | false + private function splitResource(string $resource): bool|array { if (! preg_match(self::RESOURCE_PATTERN, $resource, $matches)) { // Resource pattern failed diff --git a/modules/Install/Commands/CreateSuperadmin.php b/modules/Install/Commands/CreateSuperadmin.php new file mode 100644 index 00000000..0dfdad71 --- /dev/null +++ b/modules/Install/Commands/CreateSuperadmin.php @@ -0,0 +1,145 @@ +> + */ + private array $validationRules = []; + + #[Override] + public function run(array $params): void + { + // first, check that super admin does not exist + $userModel = model('UserModel'); + $isSuperAdminCreated = $userModel->where('is_owner', true) + ->countAllResults(); + + if ($isSuperAdminCreated > 0) { + $this->write('Super admin was already created!', 'red'); + + exit(EXIT_ERROR); + } + + $this->setValidationRules(); + + $username = $params['n'] ?? null; + $email = $params['e'] ?? null; + + $data = [ + 'is_owner' => true, + ]; + + if ($username === null) { + $username = $this->prompt('Username', null, $this->validationRules['username']['rules']); + } + + $data['username'] = $username; + + if ($email === null) { + $email = $this->prompt('Email', null, $this->validationRules['email']['rules']); + } + + $data['email'] = $email; + + $password = $this->prompt('Password', null, $this->validationRules['password']['rules']); + $passwordConfirm = $this->prompt( + 'Password confirmation', + null, + $this->validationRules['password']['rules'], + ); + + if ($password !== $passwordConfirm) { + throw new BadInputException("The passwords don't match"); + } + + $data['password'] = $password; + + // Run validation if the user has passed username and/or email via command line + $validation = service('validation'); + $validation->setRules($this->validationRules); + + if (! $validation->run($data)) { + foreach ($validation->getErrors() as $message) { + $this->write($message, 'red'); + } + + throw new CancelException('Super admin creation aborted'); + } + + $userModel = model('UserModel'); + + $user = new User($data); + $userModel->save($user); + + $user = $userModel->findById($userModel->getInsertID()); + + // set newly created user as most powerful instance group (superadmin) + $user->addGroup(setting('AuthGroups.mostPowerfulGroup')); + + $this->write('Super admin "' . $username . '" created', 'green'); + } + + private function setValidationRules(): void + { + $validationRules = new ValidationRules(); + + $rules = $validationRules->getRegistrationRules(); + + // Remove `strong_password` because it only supports use cases + // to check the user's own password. + $passwordRules = $rules['password']['rules']; + if (is_string($passwordRules)) { + $passwordRules = explode('|', $passwordRules); + } + + if (($key = array_search('strong_password[]', $passwordRules, true)) !== false) { + unset($passwordRules[$key]); + } + + if (($key = array_search('strong_password', $passwordRules, true)) !== false) { + unset($passwordRules[$key]); + } + + $config = config('Auth'); + + // Add `min_length` + $passwordRules[] = 'min_length[' . $config->minimumPasswordLength . ']'; + + $rules['password']['rules'] = $passwordRules; + + $this->validationRules = [ + 'username' => $rules['username'], + 'email' => $rules['email'], + 'password' => $rules['password'], + ]; + } +} diff --git a/modules/Install/Commands/InitDatabase.php b/modules/Install/Commands/InitDatabase.php new file mode 100644 index 00000000..67d8f61b --- /dev/null +++ b/modules/Install/Commands/InitDatabase.php @@ -0,0 +1,40 @@ +setNamespace(null) + ->latest(); + + // Seed database + $seeder = Database::seeder(); + $seeder->call('AppSeeder'); + } +} diff --git a/modules/Install/Config/Routes.php b/modules/Install/Config/Routes.php index 94a289c5..481d01fd 100644 --- a/modules/Install/Config/Routes.php +++ b/modules/Install/Config/Routes.php @@ -4,7 +4,9 @@ declare(strict_types=1); namespace Modules\Install\Config; -$routes = service('routes'); +use CodeIgniter\Router\RouteCollection; + +/** @var RouteCollection $routes */ // Install Wizard routes $routes->group( @@ -17,21 +19,21 @@ $routes->group( $routes->get('/', 'InstallController', [ 'as' => 'install', ]); - $routes->post('instance-config', 'InstallController::attemptInstanceConfig', [ + $routes->post('instance-config', 'InstallController::instanceConfigAction', [ 'as' => 'instance-config', ]); - $routes->post('database-config', 'InstallController::attemptDatabaseConfig', [ + $routes->post('database-config', 'InstallController::databaseConfigAction', [ 'as' => 'database-config', ]); - $routes->post('cache-config', 'InstallController::attemptCacheConfig', [ + $routes->post('cache-config', 'InstallController::cacheConfigAction', [ 'as' => 'cache-config', ]); $routes->post( 'create-superadmin', - 'InstallController::attemptCreateSuperAdmin', + 'InstallController::createSuperAdminAction', [ 'as' => 'create-superadmin', ], ); - } + }, ); diff --git a/modules/Install/Controllers/InstallController.php b/modules/Install/Controllers/InstallController.php index 0f80f081..f6f875af 100644 --- a/modules/Install/Controllers/InstallController.php +++ b/modules/Install/Controllers/InstallController.php @@ -10,18 +10,19 @@ declare(strict_types=1); namespace Modules\Install\Controllers; -use App\Models\UserModel; use CodeIgniter\Controller; use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; +use CodeIgniter\Shield\Entities\User; +use CodeIgniter\Shield\Exceptions\ValidationException as ShieldValidationException; use Config\Database; -use Config\Services; use Dotenv\Dotenv; use Dotenv\Exception\ValidationException; -use Modules\Auth\Entities\User; +use Modules\Auth\Models\UserModel; +use Override; use Psr\Log\LoggerInterface; use Throwable; use ViewThemes\Theme; @@ -29,17 +30,15 @@ use ViewThemes\Theme; class InstallController extends Controller { /** - * @var string[] + * @var list */ - protected $helpers = ['form', 'components', 'svg', 'misc']; + protected $helpers = ['form', 'components', 'svg', 'misc', 'setting']; - /** - * Constructor. - */ + #[Override] public function initController( RequestInterface $request, ResponseInterface $response, - LoggerInterface $logger + LoggerInterface $logger, ): void { // Do Not Edit This Line parent::initController($request, $response, $logger); @@ -75,7 +74,7 @@ class InstallController extends Controller $dotenv->required(['app.baseURL', 'analytics.salt', 'admin.gateway', 'auth.gateway']); } catch (ValidationException) { // form to input instance configuration - return $this->instanceConfig(); + return $this->instanceConfigView(); } try { @@ -87,13 +86,13 @@ class InstallController extends Controller 'database.default.DBPrefix', ]); } catch (ValidationException) { - return $this->databaseConfig(); + return $this->databaseConfigView(); } try { $dotenv->required('cache.handler'); } catch (ValidationException) { - return $this->cacheConfig(); + return $this->cacheConfigView(); } } else { try { @@ -117,23 +116,20 @@ class InstallController extends Controller try { $db = db_connect(); - // Check if superadmin has been created, meaning migrations and seeds have passed - if ( - $db->tableExists('users') && - (new UserModel())->countAll() > 0 - ) { + // Check if instance owner has been created, meaning install was completed + if ($db->tableExists('users') && new UserModel()->where('is_owner', true) + ->first() instanceof User + ) { // if so, show a 404 page throw PageNotFoundException::forPageNotFound(); } - - /** @noRector */ } catch (DatabaseException) { // Could not connect to the database // show database config view to fix value session() ->setFlashdata('error', lang('Install.messages.databaseConnectError')); - return $this->databaseConfig(); + return $this->databaseConfigView(); } // migrate if no user has been created @@ -142,39 +138,40 @@ class InstallController extends Controller // Check if all seeds have succeeded $this->seed(); - return $this->createSuperAdmin(); + return $this->createSuperAdminView(); } - public function instanceConfig(): string + public function instanceConfigView(): string { return view('instance_config'); } - public function attemptInstanceConfig(): RedirectResponse + public function instanceConfigAction(): RedirectResponse { $rules = [ - 'hostname' => 'required|validate_url', - 'media_base_url' => 'permit_empty|validate_url', - 'admin_gateway' => 'required', - 'auth_gateway' => 'required|differs[admin_gateway]', + 'hostname' => 'required|valid_url_strict', + 'media_base_url' => 'permit_empty|valid_url_strict', + 'admin_gateway' => 'required', + 'auth_gateway' => 'required|differs[admin_gateway]', ]; if (! $this->validate($rules)) { return redirect() - ->to((host_url() === null ? config('App') ->baseURL : host_url()) . config('Install')->gateway) + ->to((host_url() ?? config('App') ->baseURL) . config('Install')->gateway) ->withInput() ->with('errors', $this->validator->getErrors()); } - $baseUrl = $this->request->getPost('hostname'); - $mediaBaseUrl = $this->request->getPost('media_base_url'); + $validData = $this->validator->getValidated(); + + $baseUrl = $validData['hostname']; + $mediaBaseUrl = $validData['media_base_url']; self::writeEnv([ - 'app.baseURL' => $baseUrl, - 'app.mediaBaseURL' => - $mediaBaseUrl === '' ? $baseUrl : $mediaBaseUrl, + 'app.baseURL' => $baseUrl, + 'media.baseURL' => $mediaBaseUrl === '' ? $baseUrl : $mediaBaseUrl, 'analytics.salt' => generate_random_salt(64), - 'admin.gateway' => $this->request->getPost('admin_gateway'), - 'auth.gateway' => $this->request->getPost('auth_gateway'), + 'admin.gateway' => $validData['admin_gateway'], + 'auth.gateway' => $validData['auth_gateway'], ]); helper('text'); @@ -183,16 +180,16 @@ class InstallController extends Controller return redirect()->to(reduce_double_slashes($baseUrl . '/' . config('Install')->gateway)); } - public function databaseConfig(): string + public function databaseConfigView(): string { return view('database_config'); } - public function attemptDatabaseConfig(): RedirectResponse + public function databaseConfigAction(): RedirectResponse { $rules = [ 'db_hostname' => 'required', - 'db_name' => 'required', + 'db_name' => 'required', 'db_username' => 'required', 'db_password' => 'required', ]; @@ -204,23 +201,25 @@ class InstallController extends Controller ->with('errors', $this->validator->getErrors()); } + $validData = $this->validator->getValidated(); + self::writeEnv([ - 'database.default.hostname' => $this->request->getPost('db_hostname'), - 'database.default.database' => $this->request->getPost('db_name'), - 'database.default.username' => $this->request->getPost('db_username'), - 'database.default.password' => $this->request->getPost('db_password'), + 'database.default.hostname' => $validData['db_hostname'], + 'database.default.database' => $validData['db_name'], + 'database.default.username' => $validData['db_username'], + 'database.default.password' => $validData['db_password'], 'database.default.DBPrefix' => $this->request->getPost('db_prefix'), ]); return redirect()->back(); } - public function cacheConfig(): string + public function cacheConfigView(): string { return view('cache_config'); } - public function attemptCacheConfig(): RedirectResponse + public function cacheConfigAction(): RedirectResponse { $rules = [ 'cache_handler' => 'required', @@ -233,8 +232,10 @@ class InstallController extends Controller ->with('errors', $this->validator->getErrors()); } + $validData = $this->validator->getValidated(); + self::writeEnv([ - 'cache.handler' => $this->request->getPost('cache_handler'), + 'cache.handler' => $validData['cache_handler'], ]); return redirect()->back(); @@ -245,23 +246,9 @@ class InstallController extends Controller */ public function migrate(): void { - $migrations = Services::migrations(); + $migrate = service('migrations'); - $migrations->setNamespace('CodeIgniter\Settings') - ->latest(); - $migrations->setNamespace('Myth\Auth') - ->latest(); - $migrations->setNamespace('Modules\Fediverse') - ->latest(); - $migrations->setNamespace(APP_NAMESPACE) - ->latest(); - $migrations->setNamespace('Modules\WebSub') - ->latest(); - $migrations->setNamespace('Modules\Auth') - ->latest(); - $migrations->setNamespace('Modules\PremiumPodcasts') - ->latest(); - $migrations->setNamespace('Modules\Analytics') + $migrate->setNamespace(null) ->latest(); } @@ -279,7 +266,7 @@ class InstallController extends Controller /** * Returns the form to create a the first superadmin user for the instance. */ - public function createSuperAdmin(): string + public function createSuperAdminView(): string { return view('create_superadmin'); } @@ -289,21 +276,14 @@ class InstallController extends Controller * * After creation, user is redirected to login page to input its credentials. */ - public function attemptCreateSuperAdmin(): RedirectResponse + public function createSuperAdminAction(): RedirectResponse { - $userModel = new UserModel(); - - // Validate here first, since some things, - // like the password, can only be validated properly here. - $rules = array_merge( - $userModel->getValidationRules([ - 'only' => ['username'], - ]), - [ - 'email' => 'required|valid_email|is_unique[users.email]', - 'password' => 'required|strong_password', - ], - ); + // validate user password + $rules = [ + 'username' => 'required', + 'email' => 'required', + 'password' => 'required|strong_password', + ]; if (! $this->validate($rules)) { return redirect() @@ -312,29 +292,29 @@ class InstallController extends Controller ->with('errors', $this->validator->getErrors()); } + $validData = $this->validator->getValidated(); + // Save the user - $user = new User($this->request->getPost()); + $user = new User([ + 'username' => $validData['username'], + 'email' => $validData['email'], + 'password' => $validData['password'], + 'is_owner' => true, + ]); - // Activate user - $user->activate(); - - $db = db_connect(); - - $db->transStart(); - if (! ($userId = $userModel->insert($user, true))) { - $db->transRollback(); - - return redirect() - ->back() + $userModel = new UserModel(); + try { + $userModel->save($user); + } catch (ShieldValidationException) { + return redirect()->back() ->withInput() ->with('errors', $userModel->errors()); } - // add newly created user to superadmin group - $authorization = Services::authorization(); - $authorization->addUserToGroup($userId, 'superadmin'); + $user = $userModel->findById($userModel->getInsertID()); - $db->transComplete(); + // set newly created user as most powerful instance group (superadmin) + $user->addGroup(setting('AuthGroups.mostPowerfulGroup')); // Success! // set redirect_url session as admin area to go to after login @@ -342,7 +322,7 @@ class InstallController extends Controller ->set('redirect_url', route_to('admin')); return redirect() - ->route('login') + ->route('admin') ->with('message', lang('Install.messages.createSuperAdminSuccess')); } @@ -367,7 +347,7 @@ class InstallController extends Controller return $line; }, - $envData + $envData, ); if (! $replaced) { diff --git a/modules/Install/Language/.rsync-filter b/modules/Install/Language/.rsync-filter index 2a742b26..b802a93d 100644 --- a/modules/Install/Language/.rsync-filter +++ b/modules/Install/Language/.rsync-filter @@ -2,9 +2,11 @@ + fr/*** + pl/*** + de/*** -+ pt-BR/*** -+ nn-NO/*** ++ pt-br/*** ++ nn-no/*** + es/*** -+ zh-Hans/*** ++ zh-hans/*** + ca/*** ++ br/*** ++ sr-latn/*** - ** diff --git a/modules/Install/Language/br/Install.php b/modules/Install/Language/br/Install.php index 926c45f9..c29b77e2 100644 --- a/modules/Install/Language/br/Install.php +++ b/modules/Install/Language/br/Install.php @@ -9,10 +9,10 @@ declare(strict_types=1); */ return [ - 'title' => 'Castopod installer', + 'title' => 'Stalier Castopod', 'manual_config' => 'Kefluniañ dre zorn', 'manual_config_subtitle' => - 'Krouit ur restr `.env` gant hoc’h arventennoù ha hizivait ar bajenn evit kenderc\'hel gant ar staliañ.', + 'Krouit ur restr `.env` gant hoc’h arventennoù ha nevesait ar bajenn evit kenderc\'hel gant ar staliañ.', 'form' => [ 'instance_config' => 'Arventennoù an istañs', 'hostname' => 'Anv an ostiz', diff --git a/modules/Install/Language/da/Install.php b/modules/Install/Language/da/Install.php new file mode 100644 index 00000000..16bbbd54 --- /dev/null +++ b/modules/Install/Language/da/Install.php @@ -0,0 +1,62 @@ + 'Castopod installationsprogram', + 'manual_config' => 'Manuel konfiguration', + 'manual_config_subtitle' => + 'Opret en `.env` fil med dine indstillinger og opdater siden for at fortsætte installationen.', + 'form' => [ + 'instance_config' => 'Instanskonfiguration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Medie base URL', + 'media_base_url_hint' => + 'Hvis du bruger en CDN og/eller en ekstern analysetjeneste, kan du indstille dem her.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod skal oprette forbindelse til din MySQL (eller MariaDB) database. Hvis du ikke har disse nødvendige oplysninger, bedes du kontakte din serveradministrator.', + 'db_hostname' => 'Database host navn', + 'db_name' => 'Databasenavn', + 'db_username' => 'Database brugernavn', + 'db_password' => 'Database adgangskode', + 'db_prefix' => 'Database præfiks', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Cache handler', + 'cacheHandlerOptions' => [ + 'file' => 'File', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Næste', + 'submit' => 'Afslut installation', + 'create_superadmin' => 'Opret din Super Admin konto', + 'email' => 'Email', + 'username' => 'Brugernavn', + 'password' => 'Adgangskode', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Din superadmin konto er blevet oprettet. Log ind for at starte podcasting!', + 'databaseConnectError' => + 'Castopod kunne ikke oprette forbindelse til din database. Rediger din database konfiguration og prøv igen.', + 'writeError' => + "Kunne ikke oprette/skrive `.env` filen. Du skal oprette den manuelt ved at følge `.env.example` filskabelon i Castopod-pakken.", + ], +]; diff --git a/modules/Install/Language/de/Install.php b/modules/Install/Language/de/Install.php index b3269fdb..ecebbfa7 100644 --- a/modules/Install/Language/de/Install.php +++ b/modules/Install/Language/de/Install.php @@ -21,10 +21,10 @@ return [ 'Wenn du einen CDN und/oder einen externen Analysedienst verwendest, kannst du diesen hier festlegen.', 'admin_gateway' => 'Admin-Gateway', 'admin_gateway_hint' => - 'Die Route zum Zugriff auf den Admin-Bereich (z.B. https://example.com/cp-admin), wird standardmäßig als "cp-admin" festgelegt. Wir empfehlen, sie aus Sicherheitsgründen zu ändern.', + 'Der Pfad zum Zugriff auf den Admin-Bereich (z.B. https://example.com/cp-admin), wird standardmäßig als "cp-admin" festgelegt. Wir empfehlen, sie aus Sicherheitsgründen zu ändern.', 'auth_gateway' => 'Auth-Gateway', 'auth_gateway_hint' => - 'Die Route zum Zugriff auf die Authentifizierungsseiten (z. B. https://example.com/cp-auth), wird standardmäßig als "cp-auth" gesetzt. Wir empfehlen, sie aus Sicherheitsgründen zu ändern.', + 'Der Pfad zum Zugriff auf die Authentifizierungsseiten (z. B. https://example.com/cp-auth), wird standardmäßig als "cp-auth" gesetzt. Wir empfehlen, sie aus Sicherheitsgründen zu ändern.', 'database_config' => 'Datenbankkonfiguration', 'database_config_hint' => 'Castopod muss sich mit der MySQL-Datenbank (oder MariaDB) verbinden. Wenn diese erforderlichen Informationen nicht verfügbar sind, wende dich bitte an deinen Serveradministrator.', diff --git a/modules/Install/Language/en/Install.php b/modules/Install/Language/en/Install.php index 1f66ef11..45d26085 100644 --- a/modules/Install/Language/en/Install.php +++ b/modules/Install/Language/en/Install.php @@ -46,7 +46,7 @@ return [ ], 'next' => 'Next', 'submit' => 'Finish install', - 'create_superadmin' => 'Create your superadmin account', + 'create_superadmin' => 'Create your Super Admin account', 'email' => 'Email', 'username' => 'Username', 'password' => 'Password', diff --git a/modules/Install/Language/eu/Install.php b/modules/Install/Language/eu/Install.php new file mode 100644 index 00000000..45d26085 --- /dev/null +++ b/modules/Install/Language/eu/Install.php @@ -0,0 +1,62 @@ + 'Castopod installer', + 'manual_config' => 'Manual configuration', + 'manual_config_subtitle' => + 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'form' => [ + 'instance_config' => 'Instance configuration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', + 'db_hostname' => 'Database hostname', + 'db_name' => 'Database name', + 'db_username' => 'Database username', + 'db_password' => 'Database password', + 'db_prefix' => 'Database prefix', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Cache handler', + 'cacheHandlerOptions' => [ + 'file' => 'File', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Next', + 'submit' => 'Finish install', + 'create_superadmin' => 'Create your Super Admin account', + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Your superadmin account has been created successfully. Login to start podcasting!', + 'databaseConnectError' => + 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'writeError' => + "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + ], +]; diff --git a/modules/Install/Language/fa/Install.php b/modules/Install/Language/fa/Install.php index 1f66ef11..0164c3f8 100644 --- a/modules/Install/Language/fa/Install.php +++ b/modules/Install/Language/fa/Install.php @@ -9,13 +9,13 @@ declare(strict_types=1); */ return [ - 'title' => 'Castopod installer', - 'manual_config' => 'Manual configuration', + 'title' => 'نصب کنندهٔ کستوپاد', + 'manual_config' => 'پیکربندی دستی', 'manual_config_subtitle' => 'Create a `.env` file with your settings and refresh the page to continue installation.', 'form' => [ - 'instance_config' => 'Instance configuration', - 'hostname' => 'Hostname', + 'instance_config' => 'پیکربندی نمونه', + 'hostname' => 'نام میزبان', 'media_base_url' => 'Media base URL', 'media_base_url_hint' => 'If you use a CDN and/or an external analytics service, you may set them here.', @@ -28,28 +28,28 @@ return [ 'database_config' => 'Database configuration', 'database_config_hint' => 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', - 'db_hostname' => 'Database hostname', - 'db_name' => 'Database name', - 'db_username' => 'Database username', - 'db_password' => 'Database password', - 'db_prefix' => 'Database prefix', + 'db_hostname' => 'نام میزبان پایگاه داده', + 'db_name' => 'نام پایگاه‌داده', + 'db_username' => 'نام کاربری پایگاه‌داده', + 'db_password' => 'گذرواژهٔ پایگاه‌داده', + 'db_prefix' => 'پيشوند پايگاه‌داده', 'db_prefix_hint' => "The prefix of the Castopod table names, leave as is if you don't know what it means.", - 'cache_config' => 'Cache configuration', + 'cache_config' => 'پیکربندی انباره', 'cache_config_hint' => 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', 'cache_handler' => 'Cache handler', 'cacheHandlerOptions' => [ - 'file' => 'File', - 'redis' => 'Redis', + 'file' => 'پرونده', + 'redis' => 'ردیس', 'predis' => 'Predis', ], - 'next' => 'Next', - 'submit' => 'Finish install', - 'create_superadmin' => 'Create your superadmin account', - 'email' => 'Email', - 'username' => 'Username', - 'password' => 'Password', + 'next' => 'بعدی', + 'submit' => 'پایان نصب', + 'create_superadmin' => 'ایجاد ابرحساب مدیریتان', + 'email' => 'رایانامه', + 'username' => 'نام‌کاربری', + 'password' => 'گذرواژه', ], 'messages' => [ 'createSuperAdminSuccess' => diff --git a/modules/Install/Language/fr-ca/Install.php b/modules/Install/Language/fr-ca/Install.php new file mode 100644 index 00000000..45d26085 --- /dev/null +++ b/modules/Install/Language/fr-ca/Install.php @@ -0,0 +1,62 @@ + 'Castopod installer', + 'manual_config' => 'Manual configuration', + 'manual_config_subtitle' => + 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'form' => [ + 'instance_config' => 'Instance configuration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', + 'db_hostname' => 'Database hostname', + 'db_name' => 'Database name', + 'db_username' => 'Database username', + 'db_password' => 'Database password', + 'db_prefix' => 'Database prefix', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Cache handler', + 'cacheHandlerOptions' => [ + 'file' => 'File', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Next', + 'submit' => 'Finish install', + 'create_superadmin' => 'Create your Super Admin account', + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Your superadmin account has been created successfully. Login to start podcasting!', + 'databaseConnectError' => + 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'writeError' => + "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + ], +]; diff --git a/modules/Install/Language/fr2/Install.php b/modules/Install/Language/fr2/Install.php new file mode 100644 index 00000000..45d26085 --- /dev/null +++ b/modules/Install/Language/fr2/Install.php @@ -0,0 +1,62 @@ + 'Castopod installer', + 'manual_config' => 'Manual configuration', + 'manual_config_subtitle' => + 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'form' => [ + 'instance_config' => 'Instance configuration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', + 'db_hostname' => 'Database hostname', + 'db_name' => 'Database name', + 'db_username' => 'Database username', + 'db_password' => 'Database password', + 'db_prefix' => 'Database prefix', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Cache handler', + 'cacheHandlerOptions' => [ + 'file' => 'File', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Next', + 'submit' => 'Finish install', + 'create_superadmin' => 'Create your Super Admin account', + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Your superadmin account has been created successfully. Login to start podcasting!', + 'databaseConnectError' => + 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'writeError' => + "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + ], +]; diff --git a/modules/Install/Language/gd/Install.php b/modules/Install/Language/gd/Install.php index 1f66ef11..2c39010f 100644 --- a/modules/Install/Language/gd/Install.php +++ b/modules/Install/Language/gd/Install.php @@ -9,54 +9,54 @@ declare(strict_types=1); */ return [ - 'title' => 'Castopod installer', - 'manual_config' => 'Manual configuration', + 'title' => 'Inneal-stàlaidh Chastopod', + 'manual_config' => 'Rèiteachadh a làimh', 'manual_config_subtitle' => - 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'Cruthaich faidhle `.env` leis na roghainnean agad agus ath-nuadhaich an duilleag a leantainn air adhart leis an stàladh.', 'form' => [ - 'instance_config' => 'Instance configuration', - 'hostname' => 'Hostname', - 'media_base_url' => 'Media base URL', + 'instance_config' => 'Rèiteachadh an ionstans', + 'hostname' => 'Ainm an òstair', + 'media_base_url' => 'URL bunaiteach nam meadhanan', 'media_base_url_hint' => - 'If you use a CDN and/or an external analytics service, you may set them here.', - 'admin_gateway' => 'Admin gateway', + 'Ma tha thu a’ cleachdadh CDN agus/no seirbheis anailiseachd air an taobh a-muigh, faodaidh tu an suidheachadh an-seo.', + 'admin_gateway' => 'Balach na rianachd', 'admin_gateway_hint' => - 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', - 'auth_gateway' => 'Auth gateway', + 'Seo an t-slighe airson raon na rianachd inntrigeadh (m.e. https://ball-eisimpleir.com/cp-admin). Thèid seo a shuidheachadh air cp-admin a ghnàth ach mholamaid gun atharraich thu seo air adhbhar tèarainteachd.', + 'auth_gateway' => 'Bealach an dearbhaidh', 'auth_gateway_hint' => - 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', - 'database_config' => 'Database configuration', + 'Seo an t-slighe airson duilleagan an dearbhaidh inntrigeadh (m.e. https://ball-eisimpleir.com/cp-auth). Thèid seo a shuidheachadh air cp-auth a ghnàth ach mholamaid gun atharraich thu seo air adhbhar tèarainteachd.', + 'database_config' => 'Rèiteachadh an stòir-dhàta', 'database_config_hint' => - 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', - 'db_hostname' => 'Database hostname', - 'db_name' => 'Database name', - 'db_username' => 'Database username', - 'db_password' => 'Database password', - 'db_prefix' => 'Database prefix', + 'Feumaidh Castopod ceangal ris an stòr-dàta MySQL (no MariaDB) agad. Mur eil am fiosrachadh riatanach seo agad, cuir fios tu rianaire an fhrithealaiche agad.', + 'db_hostname' => 'Ainm òstair an stòir-dhàta', + 'db_name' => 'Ainm an stòir-dhàta', + 'db_username' => 'Ainm-cleachdaiche an stòir-dhàta', + 'db_password' => 'Facal-faire an stòir-dhàta', + 'db_prefix' => 'Ro-leasachan an stòir-dhàta', 'db_prefix_hint' => - "The prefix of the Castopod table names, leave as is if you don't know what it means.", - 'cache_config' => 'Cache configuration', + "Seo ro-leasachan do dh’ainmean clàran Chastopod, fàg e mar a tha e mur eil thu a’ tuigsinn dè as ciall dha.", + 'cache_config' => 'Rèiteachadh an tasgadain', 'cache_config_hint' => - 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', - 'cache_handler' => 'Cache handler', + 'Tagh an làimhsichear as fheàrr leat dhan tasgadan. Fàg air an luach bhunaiteach e mur eil thu a’ tuigsinn dè as ciall dha.', + 'cache_handler' => 'Làimhsichear an tasgadain', 'cacheHandlerOptions' => [ - 'file' => 'File', + 'file' => 'Faidhle', 'redis' => 'Redis', 'predis' => 'Predis', ], - 'next' => 'Next', - 'submit' => 'Finish install', - 'create_superadmin' => 'Create your superadmin account', - 'email' => 'Email', - 'username' => 'Username', - 'password' => 'Password', + 'next' => 'Air adhart', + 'submit' => 'Cuir crìoch air an stàladh', + 'create_superadmin' => 'Cruthaich an cunntas sàr-rianaire agad', + 'email' => 'Post-d', + 'username' => 'Ainm-cleachdaiche', + 'password' => 'Facal-faire', ], 'messages' => [ 'createSuperAdminSuccess' => - 'Your superadmin account has been created successfully. Login to start podcasting!', + 'Chaidh an cunntas sàr-rianaire agad a chruthachadh. Clàraich a-steach a thòiseachadh leis a’ phod-chraoladh!', 'databaseConnectError' => - 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'Cha b’ urrainn do Chastopod ceangal ris an stòr-dàta agad. Deasaich rèiteachadh an stòir-dhàta agad is feuch ris a-rithist.', 'writeError' => - "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + "Cha b’ urrainn dhuinn am faidhle `.env` a chruthachadh/sgrìobhadh thuige. Feumaidh tu a chruthachadh a làimh a leantainn ris an fhaidhle-teamplaid `.env.example` sa phacaid Castopod.", ], ]; diff --git a/modules/Install/Language/ja/Install.php b/modules/Install/Language/ja/Install.php new file mode 100644 index 00000000..45d26085 --- /dev/null +++ b/modules/Install/Language/ja/Install.php @@ -0,0 +1,62 @@ + 'Castopod installer', + 'manual_config' => 'Manual configuration', + 'manual_config_subtitle' => + 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'form' => [ + 'instance_config' => 'Instance configuration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', + 'db_hostname' => 'Database hostname', + 'db_name' => 'Database name', + 'db_username' => 'Database username', + 'db_password' => 'Database password', + 'db_prefix' => 'Database prefix', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Cache handler', + 'cacheHandlerOptions' => [ + 'file' => 'File', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Next', + 'submit' => 'Finish install', + 'create_superadmin' => 'Create your Super Admin account', + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Your superadmin account has been created successfully. Login to start podcasting!', + 'databaseConnectError' => + 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'writeError' => + "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + ], +]; diff --git a/modules/Install/Language/kk/Install.php b/modules/Install/Language/kk/Install.php new file mode 100644 index 00000000..45d26085 --- /dev/null +++ b/modules/Install/Language/kk/Install.php @@ -0,0 +1,62 @@ + 'Castopod installer', + 'manual_config' => 'Manual configuration', + 'manual_config_subtitle' => + 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'form' => [ + 'instance_config' => 'Instance configuration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', + 'db_hostname' => 'Database hostname', + 'db_name' => 'Database name', + 'db_username' => 'Database username', + 'db_password' => 'Database password', + 'db_prefix' => 'Database prefix', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Cache handler', + 'cacheHandlerOptions' => [ + 'file' => 'File', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Next', + 'submit' => 'Finish install', + 'create_superadmin' => 'Create your Super Admin account', + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Your superadmin account has been created successfully. Login to start podcasting!', + 'databaseConnectError' => + 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'writeError' => + "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + ], +]; diff --git a/modules/Install/Language/ko/Install.php b/modules/Install/Language/ko/Install.php new file mode 100644 index 00000000..1f66ef11 --- /dev/null +++ b/modules/Install/Language/ko/Install.php @@ -0,0 +1,62 @@ + 'Castopod installer', + 'manual_config' => 'Manual configuration', + 'manual_config_subtitle' => + 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'form' => [ + 'instance_config' => 'Instance configuration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', + 'db_hostname' => 'Database hostname', + 'db_name' => 'Database name', + 'db_username' => 'Database username', + 'db_password' => 'Database password', + 'db_prefix' => 'Database prefix', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Cache handler', + 'cacheHandlerOptions' => [ + 'file' => 'File', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Next', + 'submit' => 'Finish install', + 'create_superadmin' => 'Create your superadmin account', + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Your superadmin account has been created successfully. Login to start podcasting!', + 'databaseConnectError' => + 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'writeError' => + "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + ], +]; diff --git a/modules/Install/Language/nl/Install.php b/modules/Install/Language/nl/Install.php index 1f66ef11..e5cc6d4a 100644 --- a/modules/Install/Language/nl/Install.php +++ b/modules/Install/Language/nl/Install.php @@ -9,54 +9,54 @@ declare(strict_types=1); */ return [ - 'title' => 'Castopod installer', - 'manual_config' => 'Manual configuration', + 'title' => 'Castopod installatie', + 'manual_config' => 'Handmatige configuratie', 'manual_config_subtitle' => - 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'Maak een `.env` bestand aan met je instellingen en vernieuw de pagina om door te gaan met de installatie.', 'form' => [ - 'instance_config' => 'Instance configuration', - 'hostname' => 'Hostname', - 'media_base_url' => 'Media base URL', + 'instance_config' => 'Instantie configuratie', + 'hostname' => 'Hostnaam', + 'media_base_url' => 'Media basis-URL', 'media_base_url_hint' => - 'If you use a CDN and/or an external analytics service, you may set them here.', - 'admin_gateway' => 'Admin gateway', + 'Als u een CDN en/of een externe statistiekenservice gebruikt, kunt u ze hier instellen.', + 'admin_gateway' => 'Admin pad', 'admin_gateway_hint' => - 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', - 'auth_gateway' => 'Auth gateway', + 'De route naar toegang tot de admin omgeving (bijv. https://example.com/cp-admin). Het is standaard ingesteld als cp-admin, we raden je aan om het te wijzigen om veiligheidsredenen.', + 'auth_gateway' => 'Authenticatie pad', 'auth_gateway_hint' => - 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', - 'database_config' => 'Database configuration', + 'De route voor toegang tot de authenticatiepagina\'s (bijv. https://example.com/cp-auth). Deze is standaard ingesteld als cp-auth, wij raden u aan deze om veiligheidsredenen te wijzigen.', + 'database_config' => 'Databaseconfiguratie', 'database_config_hint' => - 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', - 'db_hostname' => 'Database hostname', - 'db_name' => 'Database name', - 'db_username' => 'Database username', - 'db_password' => 'Database password', - 'db_prefix' => 'Database prefix', + 'Castopod moet verbinding maken met uw MySQL (of MariaDB) database. Als u niet over de benodigde informatie beschikt, neem dan contact op met uw serverbeheerder.', + 'db_hostname' => 'Database hostnaam', + 'db_name' => 'Databasenaam', + 'db_username' => 'Database gebruikersnaam', + 'db_password' => 'Database wachtwoord', + 'db_prefix' => 'Database voorvoegsel', 'db_prefix_hint' => - "The prefix of the Castopod table names, leave as is if you don't know what it means.", - 'cache_config' => 'Cache configuration', + "Het voorvoegsel van de Castopod tabelnamen. Laat leeg indien je niet weet wat dit betekent.", + 'cache_config' => 'Cache-configuratie', 'cache_config_hint' => - 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'Kies je gewenste cache-handler. Laat deze standaard waarde achter als je geen idee hebt wat het betekent.', 'cache_handler' => 'Cache handler', 'cacheHandlerOptions' => [ - 'file' => 'File', + 'file' => 'Bestandsysteem', 'redis' => 'Redis', 'predis' => 'Predis', ], - 'next' => 'Next', - 'submit' => 'Finish install', - 'create_superadmin' => 'Create your superadmin account', - 'email' => 'Email', - 'username' => 'Username', - 'password' => 'Password', + 'next' => 'Volgende', + 'submit' => 'Installatie voltooien', + 'create_superadmin' => 'Maak uw Super Admin account aan', + 'email' => 'E-mail', + 'username' => 'Gebruikersnaam', + 'password' => 'Wachtwoord', ], 'messages' => [ 'createSuperAdminSuccess' => - 'Your superadmin account has been created successfully. Login to start podcasting!', + 'Uw superadmin account is aangemaakt. Log in om met podcasten te starten!', 'databaseConnectError' => - 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'Castopod kon geen verbinding maken met uw database. Bewerk uw databaseconfiguratie en probeer het opnieuw.', 'writeError' => - "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + "Kon het `.env` bestand niet maken/schrijven. Je moet het handmatig aanmaken door het meegeleverde voorbeeld `.env.example` bestand te kopiëren en aan te passen.", ], ]; diff --git a/modules/Install/Language/nn-NO/Install.php b/modules/Install/Language/nn-no/Install.php similarity index 100% rename from modules/Install/Language/nn-NO/Install.php rename to modules/Install/Language/nn-no/Install.php diff --git a/modules/Install/Language/pl/Install.php b/modules/Install/Language/pl/Install.php index f08df622..aa214368 100644 --- a/modules/Install/Language/pl/Install.php +++ b/modules/Install/Language/pl/Install.php @@ -16,15 +16,15 @@ return [ 'form' => [ 'instance_config' => 'Konfiguracja instancji', 'hostname' => 'Nazwa hosta', - 'media_base_url' => 'Bazowy URL multimediów', + 'media_base_url' => 'Bazowy URL mediów', 'media_base_url_hint' => - 'Jeśli korzystasz z CDN i/lub zewnętrznej usługi analitycznej, możesz ustawić je tutaj.', - 'admin_gateway' => 'Brama administracyjna', + 'Jeśli korzystasz z CDNa i/lub zewnętrznej usługi analitycznej, możesz ustawić je tutaj.', + 'admin_gateway' => 'Strona administracyjna', 'admin_gateway_hint' => - 'Droga dostępu do obszaru administracyjnego (np. https://example.com/cp-admin). Domyślnie jest ustawiona jako cp-admin, ze względów bezpieczeństwa zalecamy jej zmianę.', - 'auth_gateway' => 'Brama uwierzytelniania', + 'Dostęp do obszaru administracyjnego (np. https://example.com/cp-admin). Domyślnie jest ustawiony jako cp-admin, ale ze względów bezpieczeństwa zalecamy zmianę tej nazwy.', + 'auth_gateway' => 'Strona uwierzytelniania', 'auth_gateway_hint' => - 'Droga dostępu do stron uwierzytelniających (np. https://example.com/cp-auth). Domyślnie jest ustawiona jako cp-auth, ze względów bezpieczeństwa zalecamy jej zmianę.', + 'Dostęp do stron uwierzytelniających (np. https://example.com/cp-auth). Domyślnie jest ustawiony jako cp-auth, ale ze względów bezpieczeństwa zalecamy zmianę tej nazwy.', 'database_config' => 'Konfiguracja bazy danych', 'database_config_hint' => 'Castopod musi połączyć się z bazą danych MySQL (lub MariaDB). Jeśli nie masz tych wymaganych informacji, skontaktuj się z administratorem serwera.', @@ -34,11 +34,11 @@ return [ 'db_password' => 'Hasło bazy danych', 'db_prefix' => 'Prefiks bazy danych', 'db_prefix_hint' => - "Przedrostek nazw tabel Castopod; pozostaw bez zmian jeśli nie wiesz, co to znaczy.", + "Prefiks nazw tabel Castopod — pozostaw bez zmian, jeśli nie wiesz, co to znaczy.", 'cache_config' => 'Konfiguracja pamięci podręcznej', 'cache_config_hint' => - 'Wybierz preferowaną obsługę pamięci podręcznej (cache). Pozostaw to jako wartość domyślną, jeśli nie masz pojęcia, co to znaczy.', - 'cache_handler' => 'Obsługa pamięci podręcznej', + 'Wybierz preferowany mechanizm pamięci podręcznej. Zostaw domyślną wartość, jeśli nie masz pojęcia, co to znaczy.', + 'cache_handler' => 'Mechanizm pamięci podręcznej', 'cacheHandlerOptions' => [ 'file' => 'Plik', 'redis' => 'Redis', @@ -55,8 +55,8 @@ return [ 'createSuperAdminSuccess' => 'Twoje konto superadministratora zostało pomyślnie utworzone. Zaloguj się, aby rozpocząć podcastowanie!', 'databaseConnectError' => - 'Castopod nie mógł połączyć się z Twoją bazą danych. Edytuj konfigurację bazy danych i spróbuj ponownie.', + 'Castopod nie mógł się połączyć z Twoją bazą danych. Edytuj konfigurację bazy danych i spróbuj ponownie.', 'writeError' => - "Nie można utworzyć/zapisać pliku `.env`. Musisz go utworzyć ręcznie postępując zgodnie z szablonem pliku `.env.example` w pakiecie Castopod.", + "Nie można utworzyć/zapisać pliku `.env`. Musisz go utworzyć ręcznie, postępując zgodnie z szablonem `.env.example` w pakiecie Castopod.", ], ]; diff --git a/modules/Install/Language/pt-BR/Install.php b/modules/Install/Language/pt-br/Install.php similarity index 100% rename from modules/Install/Language/pt-BR/Install.php rename to modules/Install/Language/pt-br/Install.php diff --git a/modules/Install/Language/ro/Install.php b/modules/Install/Language/ro/Install.php new file mode 100644 index 00000000..45d26085 --- /dev/null +++ b/modules/Install/Language/ro/Install.php @@ -0,0 +1,62 @@ + 'Castopod installer', + 'manual_config' => 'Manual configuration', + 'manual_config_subtitle' => + 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'form' => [ + 'instance_config' => 'Instance configuration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', + 'db_hostname' => 'Database hostname', + 'db_name' => 'Database name', + 'db_username' => 'Database username', + 'db_password' => 'Database password', + 'db_prefix' => 'Database prefix', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Cache handler', + 'cacheHandlerOptions' => [ + 'file' => 'File', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Next', + 'submit' => 'Finish install', + 'create_superadmin' => 'Create your Super Admin account', + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Your superadmin account has been created successfully. Login to start podcasting!', + 'databaseConnectError' => + 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'writeError' => + "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + ], +]; diff --git a/modules/Install/Language/sk/Install.php b/modules/Install/Language/sk/Install.php index 1f66ef11..d6eaaf2d 100644 --- a/modules/Install/Language/sk/Install.php +++ b/modules/Install/Language/sk/Install.php @@ -25,14 +25,14 @@ return [ 'auth_gateway' => 'Auth gateway', 'auth_gateway_hint' => 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', - 'database_config' => 'Database configuration', + 'database_config' => 'Nastavenie databázy', 'database_config_hint' => 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', 'db_hostname' => 'Database hostname', - 'db_name' => 'Database name', - 'db_username' => 'Database username', - 'db_password' => 'Database password', - 'db_prefix' => 'Database prefix', + 'db_name' => 'Názov databázy', + 'db_username' => 'Prihlasovacie meno do databázy', + 'db_password' => 'Heslo k databáze', + 'db_prefix' => 'Prefix databázy', 'db_prefix_hint' => "The prefix of the Castopod table names, leave as is if you don't know what it means.", 'cache_config' => 'Cache configuration', @@ -44,18 +44,18 @@ return [ 'redis' => 'Redis', 'predis' => 'Predis', ], - 'next' => 'Next', - 'submit' => 'Finish install', + 'next' => 'Ďalej', + 'submit' => 'Dokončiť inštaláciu', 'create_superadmin' => 'Create your superadmin account', 'email' => 'Email', - 'username' => 'Username', - 'password' => 'Password', + 'username' => 'Používateľské meno', + 'password' => 'Heslo', ], 'messages' => [ 'createSuperAdminSuccess' => 'Your superadmin account has been created successfully. Login to start podcasting!', 'databaseConnectError' => - 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'Castopod sa nemohol pripojiť k vašej databáze. Upravte konfiguráciu svojej databázy a skúste to znovu.', 'writeError' => "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", ], diff --git a/modules/Install/Language/sr-latn/Install.php b/modules/Install/Language/sr-latn/Install.php new file mode 100644 index 00000000..d12ba554 --- /dev/null +++ b/modules/Install/Language/sr-latn/Install.php @@ -0,0 +1,62 @@ + 'Instalator Castopoda', + 'manual_config' => 'Ručna konfiguracija', + 'manual_config_subtitle' => + 'Napravite `.env` datoteku sa vašim podešavanjima i osvežite stranicu da bi ste nastavili instalaciju.', + 'form' => [ + 'instance_config' => 'Konfiguracija instance', + 'hostname' => 'Ime domaćina', + 'media_base_url' => 'URL medijske baze', + 'media_base_url_hint' => + 'Ako koristite CDN i/ili eksternu uslugu za analitiku, možete ih postaviti ovde.', + 'admin_gateway' => 'Administratorski izlaz', + 'admin_gateway_hint' => + 'Ruta za pristup kontrolnoj tabli administratora (eg. https://example.com/cp-admin).Podrazumevano je podešena na cp-admin, preporučujemo da je promenite iz sigurnosnih razloga.', + 'auth_gateway' => 'Auth izlaz', + 'auth_gateway_hint' => + 'Ruta za pristup stranicama za potvrdu identiteta (eg. https://example.com/cp-auth).Podrazumevano je podešena na cp-auth, preporučujemo da je promenite iz sigurnosnih razloga.', + 'database_config' => 'Konfiguracija baze podataka', + 'database_config_hint' => + 'Castopod mora da se poveže za vašom MySQL (ili MariaDB) bazom. Ukoliko ne posedujete potrebne informacije, molimo vas kontaktirajte administratora vašeg servera.', + 'db_hostname' => 'Ime hosta baze podataka', + 'db_name' => 'Ime baze podataka', + 'db_username' => 'Korisničko ime baze podataka', + 'db_password' => 'Lozinka baze podataka', + 'db_prefix' => 'Prefiks baze', + 'db_prefix_hint' => + "Prefiks imena tabela Castopod-a, ne diraj ako ne znaš šta znači.", + 'cache_config' => 'Konfiguracija keša', + 'cache_config_hint' => + 'Izaberite željeni obrađivač keša. Ostavite je kao podrazumevanu vrednost ako nemate pojma šta to znači.', + 'cache_handler' => 'Obrađivač keša', + 'cacheHandlerOptions' => [ + 'file' => 'Datoteka', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Sledeće', + 'submit' => 'Završi instalaciju', + 'create_superadmin' => 'Kreiraj svoj nalog super administratora', + 'email' => 'E-pošta', + 'username' => 'Korisničko ime', + 'password' => 'Lozinka', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Vaš nalog superadmina je uspešno kreiran. Prijavite se da biste započeli podkasting!', + 'databaseConnectError' => + 'Castopod nije mogao da se poveže sa vašom bazom podataka. Uredite konfiguraciju baze podataka i pokušajte ponovo.', + 'writeError' => + "Nije moguće kreirati/upisati datoteku `.env`. Morate je kreirati ručno prateći šablon datoteke `.env.example` u Castopod-ovom paketu.", + ], +]; diff --git a/modules/Install/Language/sv/Install.php b/modules/Install/Language/sv/Install.php index 1f66ef11..f2bc70b5 100644 --- a/modules/Install/Language/sv/Install.php +++ b/modules/Install/Language/sv/Install.php @@ -9,54 +9,54 @@ declare(strict_types=1); */ return [ - 'title' => 'Castopod installer', - 'manual_config' => 'Manual configuration', + 'title' => 'Installationsprogrammet för Castopod', + 'manual_config' => 'Manuell konfiguration', 'manual_config_subtitle' => - 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'Skapa en \'.env\' fil med dina inställningar och uppdatera sidan för att fortsätta installationen.', 'form' => [ - 'instance_config' => 'Instance configuration', - 'hostname' => 'Hostname', - 'media_base_url' => 'Media base URL', + 'instance_config' => 'Konfiguration av instans', + 'hostname' => 'Servernamn', + 'media_base_url' => 'Bas-URL för media', 'media_base_url_hint' => - 'If you use a CDN and/or an external analytics service, you may set them here.', + 'Om du använder en CDN och/eller en extern analystjänst kan du ställa in dem här.', 'admin_gateway' => 'Admin gateway', 'admin_gateway_hint' => - 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'Rutten för att komma åt adminområdet (t.ex. https://example.com/cp-admin). Det är som standard inställt som cp-admin, vi rekommenderar att du ändrar det av säkerhetsskäl.', 'auth_gateway' => 'Auth gateway', 'auth_gateway_hint' => - 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', - 'database_config' => 'Database configuration', + 'Rutten för att komma åt autentiseringssidorna (t.ex. https://example.com/cp-auth). Den är som standard inställd som cp-auth, vi rekommenderar att du ändrar den av säkerhetsskäl.', + 'database_config' => 'Databas konfiguration', 'database_config_hint' => - 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', - 'db_hostname' => 'Database hostname', - 'db_name' => 'Database name', - 'db_username' => 'Database username', - 'db_password' => 'Database password', - 'db_prefix' => 'Database prefix', + 'Castopod måste ansluta till din MySQL (eller MariaDB) databas. Om du inte har dessa nödvändiga uppgifter, kontakta din serveradministratör.', + 'db_hostname' => 'Databasens värdnamn', + 'db_name' => 'Databasnamn', + 'db_username' => 'Användarnamn till databasen', + 'db_password' => 'Databasens lösenord', + 'db_prefix' => 'Databas prefix', 'db_prefix_hint' => - "The prefix of the Castopod table names, leave as is if you don't know what it means.", - 'cache_config' => 'Cache configuration', + "Prefixet för Castopod tabellnamn, lämna som om du inte vet vad det betyder.", + 'cache_config' => 'Cache-konfiguration', 'cache_config_hint' => - 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'Välj önskad cachehanterare. Lämna det som standardvärde om du inte har någon aning om vad det innebär.', 'cache_handler' => 'Cache handler', 'cacheHandlerOptions' => [ - 'file' => 'File', + 'file' => 'Fil', 'redis' => 'Redis', 'predis' => 'Predis', ], - 'next' => 'Next', - 'submit' => 'Finish install', + 'next' => 'Nästa', + 'submit' => 'Slutför installationen', 'create_superadmin' => 'Create your superadmin account', - 'email' => 'Email', - 'username' => 'Username', - 'password' => 'Password', + 'email' => 'Epost', + 'username' => 'Användarnamn', + 'password' => 'Lösenord', ], 'messages' => [ 'createSuperAdminSuccess' => - 'Your superadmin account has been created successfully. Login to start podcasting!', + 'Ditt superadministratörskonto har skapats. Logga in för att starta podcasting!', 'databaseConnectError' => - 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'Castopod kunde inte ansluta till din databas. Redigera din databaskonfiguration och försök igen.', 'writeError' => - "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + "Kunde inte skapa/skriva `.env`-filen. Du måste skapa den manuellt genom att följa filmallen `.env.exempel` i Castopod-paketet.", ], ]; diff --git a/modules/Install/Language/uk/Install.php b/modules/Install/Language/uk/Install.php new file mode 100644 index 00000000..a06569bb --- /dev/null +++ b/modules/Install/Language/uk/Install.php @@ -0,0 +1,62 @@ + 'Кастопод інсталятор', + 'manual_config' => 'Ручне налаштування', + 'manual_config_subtitle' => + 'Створіть файл `.env` з налаштуваннями та перезавантажте сторінку, щоб продовжити встановлення.', + 'form' => [ + 'instance_config' => 'Конфігурація екземпляру', + 'hostname' => 'Ім\'я хоста', + 'media_base_url' => 'URL-адреса бази даних медіа', + 'media_base_url_hint' => + 'Якщо ви використовуєте CDN та/або зовнішній аналітичний сервіс, ви можете встановити їх тут.', + 'admin_gateway' => 'Шлюз Адміністратора', + 'admin_gateway_hint' => + 'Шлях до сторінки аутентифікації (наприклад https://example.com/cp-admin). Встановлений за замовчуванням cp-auth, Ми рекомендуємо вам змінити його з міркувань безпеки.', + 'auth_gateway' => 'Шлюз авторизації', + 'auth_gateway_hint' => + 'Шлях до сторінки аутентифікації (наприклад https://example.com/cp-auth). Встановлений за замовчуванням cp-auth, Ми рекомендуємо вам змінити його з міркувань безпеки.', + 'database_config' => 'Налаштування бази даних', + 'database_config_hint' => + 'Кастопод потребує підключення до бази даних MySQL (або MariaDB) для відновлення. Якщо у вас немає такої необхідної інформації, будь ласка, зверніться до адміністратора вашого сервера.', + 'db_hostname' => 'Ім\'я хоста бази даних', + 'db_name' => 'Назва бази даних', + 'db_username' => 'Ім\'я користувача бази даних', + 'db_password' => 'Пароль бази даних', + 'db_prefix' => 'Префікс бази даних', + 'db_prefix_hint' => + "Залишайте префікс імен таблиць Castopod, якщо ви не знаєте що це значить.", + 'cache_config' => 'Конфігурація кешу', + 'cache_config_hint' => + 'Виберіть бажаний обробник кешу або залиште без змін, як стандартне значення, якщо ви не знаєте, що це означає.', + 'cache_handler' => 'Обробник кешу', + 'cacheHandlerOptions' => [ + 'file' => 'Файл', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Далі', + 'submit' => 'Завершити установку', + 'create_superadmin' => 'Створіть свій обліковий запис головного адміністратора', + 'email' => 'Пошта', + 'username' => 'Ім\'я користувача', + 'password' => 'Пароль', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Ваш обліковий запис головного адміністратора було успішно створено. Увійдіть, щоб почати подкасти!', + 'databaseConnectError' => + 'Castopod не зміг під\'єднатися до вашої бази даних. Перевірте конфігурацію бази даних і повторіть спробу.', + 'writeError' => + "Не вдалося створити/записати файл `.env`. Ви повинні створити його вручну, перейшовши шаблон файлу `.env.example` в пакеті Castopode.", + ], +]; diff --git a/modules/Install/Language/zh-Hans/Install.php b/modules/Install/Language/zh-hans/Install.php similarity index 100% rename from modules/Install/Language/zh-Hans/Install.php rename to modules/Install/Language/zh-hans/Install.php diff --git a/modules/Install/Language/zh-hant/Install.php b/modules/Install/Language/zh-hant/Install.php new file mode 100644 index 00000000..45d26085 --- /dev/null +++ b/modules/Install/Language/zh-hant/Install.php @@ -0,0 +1,62 @@ + 'Castopod installer', + 'manual_config' => 'Manual configuration', + 'manual_config_subtitle' => + 'Create a `.env` file with your settings and refresh the page to continue installation.', + 'form' => [ + 'instance_config' => 'Instance configuration', + 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', + 'admin_gateway' => 'Admin gateway', + 'admin_gateway_hint' => + 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', + 'auth_gateway' => 'Auth gateway', + 'auth_gateway_hint' => + 'The route to access the authentication pages (eg. https://example.com/cp-auth). It is set by default as cp-auth, we recommend you change it for security reasons.', + 'database_config' => 'Database configuration', + 'database_config_hint' => + 'Castopod needs to connect to your MySQL (or MariaDB) database. If you do not have these required info, please contact your server administrator.', + 'db_hostname' => 'Database hostname', + 'db_name' => 'Database name', + 'db_username' => 'Database username', + 'db_password' => 'Database password', + 'db_prefix' => 'Database prefix', + 'db_prefix_hint' => + "The prefix of the Castopod table names, leave as is if you don't know what it means.", + 'cache_config' => 'Cache configuration', + 'cache_config_hint' => + 'Choose your preferred cache handler. Leave it as the default value if you have no clue what it means.', + 'cache_handler' => 'Cache handler', + 'cacheHandlerOptions' => [ + 'file' => 'File', + 'redis' => 'Redis', + 'predis' => 'Predis', + ], + 'next' => 'Next', + 'submit' => 'Finish install', + 'create_superadmin' => 'Create your Super Admin account', + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + ], + 'messages' => [ + 'createSuperAdminSuccess' => + 'Your superadmin account has been created successfully. Login to start podcasting!', + 'databaseConnectError' => + 'Castopod could not connect to your database. Edit your database configuration and try again.', + 'writeError' => + "Couldn't create/write the `.env` file. You must create it manually by following the `.env.example` file template in the Castopod package.", + ], +]; diff --git a/modules/Media/Config/Media.php b/modules/Media/Config/Media.php new file mode 100644 index 00000000..987847c4 --- /dev/null +++ b/modules/Media/Config/Media.php @@ -0,0 +1,73 @@ + + */ + public array $fileManagers = [ + 'fs' => FS::class, + 's3' => S3::class, + ]; + + /** + * @var array + */ + public array $s3 = [ + 'bucket' => 'castopod', + 'key' => '', + 'secret' => '', + 'region' => '', + 'protocol' => '', + 'endpoint' => '', + 'debug' => false, + 'pathStyleEndpoint' => false, + 'keyPrefix' => '', + ]; + + /** + * -------------------------------------------------------------------------- + * Media Base URL + * -------------------------------------------------------------------------- + * + * URL to your media root. Typically this will be your base URL, + * WITH a trailing slash: + * + * http://cdn.example.com/ + */ + public string $baseURL = 'http://localhost:8080/'; + + /** + * -------------------------------------------------------------------------- + * Media root folder + * -------------------------------------------------------------------------- + * Defines the root folder for media files storage + */ + public string $root = 'media'; + + /** + * -------------------------------------------------------------------------- + * Media storage folder + * -------------------------------------------------------------------------- + * Defines the folder used to store the media root folder + */ + public string $storage = ROOTPATH . 'public'; + + /** + * @var array + */ + public array $folders = [ + 'podcasts' => 'podcasts', + 'persons' => 'persons', + ]; +} diff --git a/app/Libraries/Vite/Config/Services.php b/modules/Media/Config/Services.php similarity index 54% rename from app/Libraries/Vite/Config/Services.php rename to modules/Media/Config/Services.php index cd5adfde..8b5fd814 100644 --- a/app/Libraries/Vite/Config/Services.php +++ b/modules/Media/Config/Services.php @@ -2,10 +2,11 @@ declare(strict_types=1); -namespace Vite\Config; +namespace Modules\Media\Config; use CodeIgniter\Config\BaseService; -use Vite\Vite; +use Exception; +use Modules\Media\FileManagers\FileManagerInterface; /** * Services Configuration file. @@ -19,12 +20,21 @@ use Vite\Vite; */ class Services extends BaseService { - public static function vite(bool $getShared = true): Vite + public static function file_manager(bool $getShared = true): FileManagerInterface { if ($getShared) { - return self::getSharedInstance('vite'); + return self::getSharedInstance('file_manager'); } - return new Vite(); + $config = config('Media'); + $fileManagerClass = $config->fileManagers[$config->fileManager]; + + $fileManager = new $fileManagerClass($config); + + if ($fileManager instanceof FileManagerInterface) { + return $fileManager; + } + + throw new Exception('File Manager service must extend FileManagerInterface'); } } diff --git a/app/Database/Migrations/2020-05-29-120000_add_media.php b/modules/Media/Database/Migrations/2021-05-29-120000_add_media.php similarity index 67% rename from app/Database/Migrations/2020-05-29-120000_add_media.php rename to modules/Media/Database/Migrations/2021-05-29-120000_add_media.php index 177827c5..d7409038 100644 --- a/app/Database/Migrations/2020-05-29-120000_add_media.php +++ b/modules/Media/Database/Migrations/2021-05-29-120000_add_media.php @@ -8,31 +8,33 @@ declare(strict_types=1); * @link https://castopod.org/ */ -namespace App\Database\Migrations; +namespace Media\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddMedia extends Migration +class AddMedia extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'id' => [ - 'type' => 'INT', - 'unsigned' => true, + 'type' => 'INT', + 'unsigned' => true, 'auto_increment' => true, ], 'file_path' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 255, ], 'file_size' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, - 'comment' => 'File size in bytes', + 'comment' => 'File size in bytes', ], 'file_mimetype' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 45, ], 'file_metadata' => [ @@ -40,26 +42,28 @@ class AddMedia extends Migration 'null' => true, ], 'type' => [ - 'type' => 'ENUM', + 'type' => 'ENUM', 'constraint' => ['image', 'audio', 'video', 'transcript', 'chapters', 'document'], - 'default' => 'document', + 'default' => 'document', ], 'description' => [ 'type' => 'TEXT', 'null' => true, ], 'language_code' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 2, - 'null' => true, + 'null' => true, ], 'uploaded_by' => [ - 'type' => 'INT', - 'unsigned' => true, + 'type' => 'INT', + 'constraint' => 11, + 'unsigned' => true, ], 'updated_by' => [ - 'type' => 'INT', - 'unsigned' => true, + 'type' => 'INT', + 'constraint' => 11, + 'unsigned' => true, ], 'uploaded_at' => [ 'type' => 'DATETIME', @@ -76,6 +80,7 @@ class AddMedia extends Migration $this->forge->createTable('media'); } + #[Override] public function down(): void { $this->forge->dropTable('media'); diff --git a/modules/Media/Database/Migrations/2022-30-12-180000_rename_media_file_path.php b/modules/Media/Database/Migrations/2022-30-12-180000_rename_media_file_path.php new file mode 100644 index 00000000..14b54394 --- /dev/null +++ b/modules/Media/Database/Migrations/2022-30-12-180000_rename_media_file_path.php @@ -0,0 +1,43 @@ + [ + 'name' => 'file_key', + 'type' => 'VARCHAR', + 'constraint' => 255, + ], + ]; + $this->forge->modifyColumn('media', $fields); + } + + #[Override] + public function down(): void + { + $fields = [ + 'file_key' => [ + 'name' => 'file_path', + 'type' => 'VARCHAR', + 'constraint' => 255, + ], + ]; + $this->forge->modifyColumn('media', $fields); + } +} diff --git a/app/Entities/Media/Audio.php b/modules/Media/Entities/Audio.php similarity index 63% rename from app/Entities/Media/Audio.php rename to modules/Media/Entities/Audio.php index 29b304b3..2219a537 100644 --- a/app/Entities/Media/Audio.php +++ b/modules/Media/Entities/Audio.php @@ -8,10 +8,11 @@ declare(strict_types=1); * @link https://castopod.org/ */ -namespace App\Entities\Media; +namespace Modules\Media\Entities; use CodeIgniter\Files\File; use JamesHeinrich\GetID3\GetID3; +use Override; /** * @property float $duration @@ -21,34 +22,32 @@ class Audio extends BaseMedia { protected string $type = 'audio'; - /** - * @param array|null $data - */ - public function __construct(array $data = null) + #[Override] + public function initFileProperties(): void { - parent::__construct($data); + parent::initFileProperties(); - if ($this->file_metadata) { + if ($this->file_metadata !== null) { $this->duration = (float) $this->file_metadata['playtime_seconds']; $this->header_size = (int) $this->file_metadata['avdataoffset']; } } + #[Override] public function setFile(File $file): self { parent::setFile($file); $getID3 = new GetID3(); - $audioMetadata = $getID3->analyze(media_path($this->file_path)); - - // remove heavy image data from metadata - unset($audioMetadata['comments']['picture']); - unset($audioMetadata['id3v2']['APIC']); + $audioMetadata = $getID3->analyze($file->getRealPath()); $this->attributes['file_mimetype'] = $audioMetadata['mime_type']; $this->attributes['file_size'] = $audioMetadata['filesize']; $this->attributes['description'] = @$audioMetadata['id3v2']['comments']['comment'][0]; - $this->attributes['file_metadata'] = json_encode($audioMetadata, JSON_INVALID_UTF8_SUBSTITUTE); + $this->attributes['file_metadata'] = json_encode([ + 'playtime_seconds' => $audioMetadata['playtime_seconds'], + 'avdataoffset' => $audioMetadata['avdataoffset'], + ], JSON_INVALID_UTF8_SUBSTITUTE); return $this; } diff --git a/modules/Media/Entities/BaseMedia.php b/modules/Media/Entities/BaseMedia.php new file mode 100644 index 00000000..5eb1b118 --- /dev/null +++ b/modules/Media/Entities/BaseMedia.php @@ -0,0 +1,140 @@ +|null $file_metadata + * @property 'image'|'audio'|'video'|'document' $type + * @property string|null $description + * @property string|null $language_code + * @property int $uploaded_by + * @property int $updated_by + */ +class BaseMedia extends Entity +{ + protected File $file; + + /** + * @var list + */ + protected $dates = ['uploaded_at', 'updated_at']; + + /** + * @var array + */ + protected $casts = [ + 'id' => 'integer', + 'file_key' => 'string', + 'file_size' => 'int', + 'file_mimetype' => 'string', + 'file_metadata' => '?json-array', + 'type' => 'string', + 'description' => '?string', + 'language_code' => '?string', + 'uploaded_by' => 'integer', + 'updated_by' => 'integer', + ]; + + /** + * @param array $data + */ + #[Override] + public function injectRawData(array $data): static + { + parent::injectRawData($data); + + $this->initFileProperties(); + + return $this; + } + + public function initFileProperties(): void + { + $pathInfo = pathinfo($this->file_key) + [ + 'filename' => '', + 'dirname' => '', + 'extension' => '', + ]; + + $this->file_url = service('file_manager') + ->getUrl($this->file_key); + $this->file_name = $pathInfo['filename']; + $this->file_directory = $pathInfo['dirname']; + $this->file_extension = $pathInfo['extension']; + } + + public function setFile(File $file): self + { + $this->attributes['type'] = $this->type; + $this->attributes['file_mimetype'] = $file->getMimeType(); + $this->attributes['file_metadata'] = json_encode(lstat((string) $file), JSON_INVALID_UTF8_IGNORE); + + if ($filesize = $file->getSize()) { + $this->attributes['file_size'] = $filesize; + } + + $this->attributes['file'] = $file; + + return $this; + } + + public function saveFile(): void + { + if (! $this->attributes['file'] || ! $this->file_key) { + throw new RuntimeException("'file' and 'file_key' attributes must be set before saving a file."); + } + + $this->attributes['file_key'] = service('file_manager')->save($this->attributes['file'], $this->file_key); + } + + public function deleteFile(): bool + { + return service('file_manager')->delete($this->file_key); + } + + public function rename(): bool + { + $newFileKey = $this->file_directory . '/' . new File('')->getRandomName() . '.' . $this->file_extension; + + $db = db_connect(); + $db->transStart(); + + if (! new MediaModel()->update($this->id, [ + 'file_key' => $newFileKey, + ])) { + return false; + } + + if (! service('file_manager')->rename($this->file_key, $newFileKey)) { + $db->transRollback(); + + return false; + } + + $db->transComplete(); + + return true; + } +} diff --git a/modules/Media/Entities/Chapters.php b/modules/Media/Entities/Chapters.php new file mode 100644 index 00000000..44cbfc54 --- /dev/null +++ b/modules/Media/Entities/Chapters.php @@ -0,0 +1,65 @@ +file_metadata !== null && array_key_exists('chapter_count', $this->file_metadata)) { + helper('media'); + + $this->chapter_count = $this->file_metadata['chapter_count']; + } + } + + #[Override] + public function setFile(File $file): self + { + parent::setFile($file); + + $metadata = lstat((string) $file); + + if (! $metadata) { + $metadata = []; + } + + helper('filesystem'); + + $metadata['chapter_count'] = $this->countChaptersInJson($file); + + $this->attributes['file_metadata'] = json_encode($metadata, JSON_INVALID_UTF8_IGNORE); + + $this->file = $file; + + return $this; + } + + private function countChaptersInJson(File $file): Int + { + $chapterContent = file_get_contents($file->getRealPath()); + + if ($chapterContent === false) { + throw new Exception('Could not read chapter file at ' . $this->file->getRealPath()); + } + + return substr_count($chapterContent, 'startTime'); + } +} diff --git a/app/Entities/Media/Document.php b/modules/Media/Entities/Document.php similarity index 88% rename from app/Entities/Media/Document.php rename to modules/Media/Entities/Document.php index 705f2a3a..7a540862 100644 --- a/app/Entities/Media/Document.php +++ b/modules/Media/Entities/Document.php @@ -8,7 +8,7 @@ declare(strict_types=1); * @link https://castopod.org/ */ -namespace App\Entities\Media; +namespace Modules\Media\Entities; class Document extends BaseMedia { diff --git a/modules/Media/Entities/Image.php b/modules/Media/Entities/Image.php new file mode 100644 index 00000000..02319af1 --- /dev/null +++ b/modules/Media/Entities/Image.php @@ -0,0 +1,183 @@ +> $sizes + */ +class Image extends BaseMedia +{ + protected string $type = 'image'; + + /** + * @var array> + */ + protected array $sizes = []; + + #[Override] + public function initFileProperties(): void + { + parent::initFileProperties(); + + if ($this->file_metadata && array_key_exists('sizes', $this->file_metadata)) { + $this->sizes = $this->file_metadata['sizes']; + $this->initSizeProperties(); + } + } + + public function initSizeProperties(): bool + { + helper('filesystem'); + + foreach ($this->sizes as $name => $size) { + $extension = array_key_exists('extension', $size) ? $size['extension'] : $this->file_extension; + $mimetype = array_key_exists('mimetype', $size) ? $size['mimetype'] : $this->file_mimetype; + $width = array_key_exists('width', $size) ? $size['width'] : 0; + $height = array_key_exists('height', $size) ? $size['height'] : 0; + + $this->{$name . '_key'} = change_file_path($this->file_key, '_' . $name, $extension); + $this->{$name . '_url'} = service('file_manager')->getUrl($this->{$name . '_key'}); + $this->{$name . '_mimetype'} = $mimetype; + $this->{$name . '_width'} = $width; + $this->{$name . '_height'} = $height; + } + + return true; + } + + /** + * @param array $data + */ + #[Override] + public function injectRawData(array $data): static + { + parent::injectRawData($data); + + if ($this->attributes === []) { + return $this; + } + + if ($this->file_metadata !== [] && array_key_exists('sizes', $this->file_metadata)) { + $this->sizes = $this->file_metadata['sizes']; + $this->attributes['sizes'] = $this->file_metadata['sizes']; + $this->initFileProperties(); + $this->initSizeProperties(); + } + + return $this; + } + + #[Override] + public function setFile(File $file): self + { + parent::setFile($file); + + if ($this->file_mimetype === 'image/jpeg' && $metadata = @exif_read_data( + $file->getRealPath(), + null, + true, + )) { + $metadata['sizes'] = $this->attributes['sizes']; + $this->attributes['file_size'] = $metadata['FILE']['FileSize']; + } else { + $metadata = [ + 'sizes' => $this->attributes['sizes'], + ]; + } + + $this->attributes['file_metadata'] = json_encode($metadata, JSON_INVALID_UTF8_IGNORE); + + return $this; + } + + #[Override] + public function saveFile(): void + { + if ($this->attributes['sizes'] !== []) { + $this->initFileProperties(); + $this->saveSizes(); + } + + parent::saveFile(); + } + + #[Override] + public function deleteFile(): bool + { + if (parent::deleteFile()) { + return $this->deleteSizes(); + } + + return false; + } + + public function saveSizes(): void + { + $tempImagePath = ''; + if (! array_key_exists('file', $this->attributes) && $this->file_key) { + // no original file instance set to save sizes from + + // download image temporarily to generate sizes from + $tempImagePath = (string) tempnam(WRITEPATH . 'temp', 'img_'); + $imageContent = (string) service('file_manager') + ->getFileContents($this->file_key); + file_put_contents($tempImagePath, $imageContent); + + $this->attributes['file'] = new File($tempImagePath, true); + } + + // save derived sizes + $imageService = service('image'); + + foreach ($this->sizes as $name => $size) { + $tempFilePath = tempnam(WRITEPATH . 'temp', 'img_'); + $resizedImage = $imageService + ->withFile($this->attributes['file']->getRealPath()) + ->resize($size['width'], $size['height']); + + /** @var GdImage $resizedImageResource */ + $resizedImageResource = $resizedImage->getResource(); + + // set resolution to 72 by 72 for all sizes + // Apple Podcasts requires images to be 72 dpi + imageresolution($resizedImageResource, 72, 72); + + $resizedImage->save($tempFilePath); + + $newImage = new File($tempFilePath, true); + + service('file_manager') + ->save($newImage, $this->{$name . '_key'}); + } + + if ($tempImagePath !== '') { + unlink($tempImagePath); + } + } + + private function deleteSizes(): bool + { + // delete all derived sizes + foreach (array_keys($this->sizes) as $name) { + $pathProperty = $name . '_key'; + + if (! service('file_manager')->delete($this->{$pathProperty})) { + return false; + } + } + + return true; + } +} diff --git a/modules/Media/Entities/Transcript.php b/modules/Media/Entities/Transcript.php new file mode 100644 index 00000000..160d9551 --- /dev/null +++ b/modules/Media/Entities/Transcript.php @@ -0,0 +1,112 @@ +file_metadata !== null && array_key_exists('json_key', $this->file_metadata)) { + helper('media'); + + $this->json_key = $this->file_metadata['json_key']; + $this->json_url = service('file_manager') + ->getUrl($this->json_key); + } + } + + #[Override] + public function setFile(File $file): self + { + parent::setFile($file); + + $metadata = lstat((string) $file); + + if (! $metadata) { + $metadata = []; + } + + helper('filesystem'); + + // set metadata (generated json file path) + $this->json_key = change_file_path($this->file_key, '', 'json'); + $metadata['json_key'] = $this->json_key; + + $this->attributes['file_metadata'] = json_encode($metadata, JSON_INVALID_UTF8_IGNORE); + + $this->file = $file; + + return $this; + } + + #[Override] + public function saveFile(): void + { + $this->saveJsonTranscript(); + + parent::saveFile(); + } + + #[Override] + public function deleteFile(): bool + { + if (! parent::deleteFile()) { + return false; + } + + if ($this->json_key) { + return service('file_manager')->delete($this->json_key); + } + + return true; + } + + private function saveJsonTranscript(): void + { + $transcriptContent = file_get_contents($this->file->getRealPath()); + + $transcriptParser = new TranscriptParser(); + + if ($transcriptContent === false) { + throw new Exception('Could not read transcript file at ' . $this->file->getRealPath()); + } + + $transcript_format = $this->file->getExtension(); + $transcriptJson = match ($transcript_format) { + 'vtt' => $transcriptParser->loadString($transcriptContent) + ->parseVtt(), + default => $transcriptParser->loadString($transcriptContent) + ->parseSrt(), + }; + + $tempFilePath = WRITEPATH . 'uploads/' . $this->file->getRandomName(); + file_put_contents($tempFilePath, $transcriptJson); + + $newTranscriptJson = new File($tempFilePath, true); + + service('file_manager') + ->save($newTranscriptJson, $this->json_key); + } +} diff --git a/app/Entities/Media/Video.php b/modules/Media/Entities/Video.php similarity index 87% rename from app/Entities/Media/Video.php rename to modules/Media/Entities/Video.php index dd7f8658..5c2d692b 100644 --- a/app/Entities/Media/Video.php +++ b/modules/Media/Entities/Video.php @@ -8,7 +8,7 @@ declare(strict_types=1); * @link https://castopod.org/ */ -namespace App\Entities\Media; +namespace Modules\Media\Entities; class Video extends BaseMedia { diff --git a/modules/Media/FileManagers/FS.php b/modules/Media/FileManagers/FS.php new file mode 100644 index 00000000..8fbb918e --- /dev/null +++ b/modules/Media/FileManagers/FS.php @@ -0,0 +1,159 @@ +media_path_absolute($key); + + if (! file_exists(dirname($path))) { + mkdir(dirname($path), 0777, true); + } + + if (! file_exists(dirname($path) . '/index.html')) { + touch(dirname($path) . '/index.html'); + } + + // copy to media folder, overwrite file if already existing + $isCopySuccessful = copy($file->getRealPath(), $path); + + if (! $isCopySuccessful) { + throw new Exception("Could not save file {$key} to {$path}"); + } + + // delete temporary file after copy + unlink($file->getRealPath()); + + return $key; + } + + #[Override] + public function delete(string $key): bool + { + helper('media'); + + return @unlink($this->media_path_absolute($key)); + } + + #[Override] + public function getUrl(string $key): string + { + return media_url($this->config->root . '/' . $key); + } + + #[Override] + public function rename(string $oldKey, string $newKey): bool + { + helper('media'); + + return rename($this->media_path_absolute($oldKey), $this->media_path_absolute($newKey)); + } + + #[Override] + public function getFileContents(string $key): string|false + { + helper('media'); + + return file_get_contents($this->media_path_absolute($key)); + } + + #[Override] + public function getFileInput(string $key): string + { + helper('media'); + + return $this->media_path_absolute($key); + } + + #[Override] + public function deletePodcastImageSizes(string $podcastHandle): bool + { + foreach (['jpg', 'jpeg', 'png', 'webp'] as $ext) { + $this->deleteAll("podcasts/{$podcastHandle}", "*_*{$ext}"); + } + + return true; + } + + #[Override] + public function deletePersonImagesSizes(): bool + { + foreach (['jpg', 'jpeg', 'png', 'webp'] as $ext) { + $this->deleteAll('persons', "*_*{$ext}"); + } + + return true; + } + + #[Override] + public function deleteAll(string $prefix, string $pattern = '*'): bool + { + helper('media'); + + // delete all in folder? + if ($pattern === '*') { + helper('filesystem'); + + return delete_files($this->media_path_absolute($prefix), true); + } + + $prefix = rtrim($prefix, '/') . '/'; + + $imagePaths = glob($this->media_path_absolute($prefix . $pattern)); + + if (! $imagePaths) { + return true; + } + + foreach ($imagePaths as $imagePath) { + @unlink($imagePath); + } + + return true; + } + + #[Override] + public function isHealthy(): bool + { + helper('media'); + + return is_really_writable($this->media_path_absolute()); + } + + /** + * Prefixes the absolute storage directory to the media path of a given uri + * + * @param string|string[] $uri URI string or array of URI segments + */ + private function media_path_absolute(string | array $uri = ''): string + { + // convert segment array to string + if (is_array($uri)) { + $uri = implode('/', $uri); + } + + $uri = trim($uri, '/'); + + return config('Media')->storage . '/' . config('Media')->root . '/' . $uri; + } +} diff --git a/modules/Media/FileManagers/FileManagerInterface.php b/modules/Media/FileManagers/FileManagerInterface.php new file mode 100644 index 00000000..d711230d --- /dev/null +++ b/modules/Media/FileManagers/FileManagerInterface.php @@ -0,0 +1,30 @@ +s3 = new S3Client([ + 'version' => 'latest', + 'region' => $config->s3['region'], + 'endpoint' => $config->s3['endpoint'], + 'credentials' => new Credentials((string) $config->s3['key'], (string) $config->s3['secret']), + 'debug' => $config->s3['debug'], + 'use_path_style_endpoint' => $config->s3['pathStyleEndpoint'], + ]); + } + + #[Override] + public function save(File $file, string $key): string + { + $this->s3->putObject([ + 'Bucket' => $this->config->s3['bucket'], + 'Key' => $this->prefixKey($key), + 'SourceFile' => $file, + 'ContentType' => $file->getMimeType(), + 'CacheControl' => 'max-age=' . YEAR, + 'ACL' => 'public-read', + ]); + + // delete file after storage in s3 + unlink($file->getRealPath()); + + return $key; + } + + #[Override] + public function delete(string $key): bool + { + try { + $this->s3->deleteObject([ + 'Bucket' => $this->config->s3['bucket'], + 'Key' => $this->prefixKey($key), + ]); + } catch (Exception) { + return false; + } + + return true; + } + + #[Override] + public function getUrl(string $key): string + { + return media_url($this->prefixKey($key)); + } + + #[Override] + public function rename(string $oldKey, string $newKey): bool + { + try { + // copy old object with new key + $this->s3->copyObject([ + 'Bucket' => $this->config->s3['bucket'], + 'CopySource' => $this->config->s3['bucket'] . '/' . $this->prefixKey($oldKey), + 'Key' => $this->prefixKey($newKey), + 'ACL' => 'public-read', + ]); + } catch (Exception) { + return false; + } + + // delete old object + return $this->delete($oldKey); + } + + #[Override] + public function getFileContents(string $key): string|false + { + try { + $result = $this->s3->getObject([ + 'Bucket' => $this->config->s3['bucket'], + 'Key' => $this->prefixKey($key), + ]); + } catch (Exception) { + return false; + } + + return (string) $result->get('Body'); + } + + #[Override] + public function getFileInput(string $key): string + { + return $this->getUrl($key); + } + + #[Override] + public function deletePodcastImageSizes(string $podcastHandle): bool + { + foreach (['jpg', 'jpeg', 'png', 'webp'] as $ext) { + $this->deleteAll('podcasts/' . $podcastHandle, "*_*{$ext}"); + } + + return true; + } + + #[Override] + public function deletePersonImagesSizes(): bool + { + foreach (['jpg', 'jpeg', 'png', 'webp'] as $ext) { + $this->deleteAll('persons', "*_*{$ext}"); + } + + return true; + } + + #[Override] + public function deleteAll(string $prefix, ?string $pattern = '*'): bool + { + $prefix = rtrim($this->prefixKey($prefix), '/') . '/'; // make sure that there is a trailing slash + $pattern = $prefix . $pattern; + + $results = $this->s3->getPaginator('ListObjectsV2', [ + 'Bucket' => $this->config->s3['bucket'], + 'prefix' => $prefix, + ]); + + $objectsToDelete = []; + foreach ($results as $result) { + if ($result['Contents'] === null) { + continue; + } + + foreach ($result['Contents'] as $object) { + if (fnmatch($pattern, $object['Key'])) { + $objectsToDelete[] = [ + 'Key' => $object['Key'], + ]; + } + } + } + + if ($objectsToDelete === []) { + return true; + } + + try { + $this->s3->deleteObjects([ + 'Bucket' => $this->config->s3['bucket'], + 'Delete' => [ + 'Objects' => $objectsToDelete, + ], + ]); + } catch (Exception) { + return false; + } + + return true; + } + + #[Override] + public function isHealthy(): bool + { + // check that bucket exists + if (! $this->s3->doesBucketExist((string) $this->config->s3['bucket'])) { + return false; + } + + try { + // ok if bucket exists and you have permission to access it + $this->s3->headBucket([ + 'Bucket' => $this->config->s3['bucket'], + ]); + } catch (Exception) { + return false; + } + + return true; + } + + private function prefixKey(string $key): string + { + if ($this->config->s3['keyPrefix'] === '') { + return $key; + } + + $keyPrefix = rtrim((string) $this->config->s3['keyPrefix']); + + return $keyPrefix . '/' . $key; + } +} diff --git a/modules/Media/Helpers/filesystem_helper.php b/modules/Media/Helpers/filesystem_helper.php new file mode 100644 index 00000000..9a6d00c8 --- /dev/null +++ b/modules/Media/Helpers/filesystem_helper.php @@ -0,0 +1,20 @@ +|string $relativePath URI string or array of URI segments + */ + function media_url(array|string $relativePath = '', ?string $scheme = null): string + { + // Convert array of segments to a string + if (is_array($relativePath)) { + $relativePath = implode('/', $relativePath); + } + + $uri = new URI(rtrim(config('Media')->baseURL, '/') . '/' . ltrim($relativePath)); + + return URI::createURIString( + $scheme ?? $uri->getScheme(), + $uri->getAuthority(), + $uri->getPath(), + $uri->getQuery(), + $uri->getFragment(), + ); + } +} diff --git a/app/Models/MediaModel.php b/modules/Media/Models/MediaModel.php similarity index 65% rename from app/Models/MediaModel.php rename to modules/Media/Models/MediaModel.php index 7acb9608..d6b948a6 100644 --- a/app/Models/MediaModel.php +++ b/modules/Media/Models/MediaModel.php @@ -8,18 +8,18 @@ declare(strict_types=1); * @link https://castopod.org/ */ -namespace App\Models; +namespace Modules\Media\Models; -use App\Entities\Media\Audio; -use App\Entities\Media\Chapters; -use App\Entities\Media\Document; -use App\Entities\Media\Image; -use App\Entities\Media\Transcript; -use App\Entities\Media\Video; use CodeIgniter\Database\BaseResult; use CodeIgniter\Database\ConnectionInterface; use CodeIgniter\Model; use CodeIgniter\Validation\ValidationInterface; +use Modules\Media\Entities\Audio; +use Modules\Media\Entities\Chapters; +use Modules\Media\Entities\Document; +use Modules\Media\Entities\Image; +use Modules\Media\Entities\Transcript; +use Modules\Media\Entities\Video; class MediaModel extends Model { @@ -28,9 +28,6 @@ class MediaModel extends Model */ protected $table = 'media'; - /** - * @noRector - */ protected $returnType = Document::class; /** @@ -51,11 +48,11 @@ class MediaModel extends Model protected $createdField = 'uploaded_at'; /** - * @var string[] + * @var list */ protected $allowedFields = [ 'id', - 'file_path', + 'file_key', 'file_size', 'file_mimetype', 'file_metadata', @@ -69,46 +66,32 @@ class MediaModel extends Model /** * clear cache before update if by any chance, the podcast name changes, so will the podcast link * - * @var string[] + * @var list */ protected $beforeUpdate = ['clearCache']; /** - * @var string[] + * @var list */ protected $beforeDelete = ['clearCache']; /** - * Model constructor. - * * @param ConnectionInterface|null $db DB Connection * @param ValidationInterface|null $validation Validation */ public function __construct( protected string $fileType = 'document', - ConnectionInterface &$db = null, - ValidationInterface $validation = null + ?ConnectionInterface &$db = null, + ?ValidationInterface $validation = null, ) { - switch ($fileType) { - case 'audio': - $this->returnType = Audio::class; - break; - case 'video': - $this->returnType = Video::class; - break; - case 'image': - $this->returnType = Image::class; - break; - case 'transcript': - $this->returnType = Transcript::class; - break; - case 'chapters': - $this->returnType = Chapters::class; - break; - default: - // do nothing, keep Document class as default - break; - } + $this->returnType = match ($fileType) { + 'audio' => Audio::class, + 'video' => Video::class, + 'image' => Image::class, + 'transcript' => Transcript::class, + 'chapters' => Chapters::class, + default => Document::class, + }; parent::__construct($db, $validation); } @@ -117,14 +100,7 @@ class MediaModel extends Model { $cacheName = "media#{$mediaId}"; if (! ($found = cache($cacheName))) { - $builder = $this->where([ - 'id' => $mediaId, - ]); - - /** @var object $result */ - $result = $builder->first(); - $mediaClass = $this->returnType; - $found = new $mediaClass($result->toArray(false, true)); + $found = $this->find($mediaId); cache() ->save($cacheName, $found, DECADE); @@ -135,13 +111,19 @@ class MediaModel extends Model /** * @param Document|Audio|Video|Image|Transcript|Chapters $media - * - * @noRector ReturnTypeDeclarationRector */ public function saveMedia(object $media): int | false { + // save file first + $media->saveFile(); + // insert record in database - if (! $mediaId = $this->insert($media, true)) { + /** @var int|false $mediaId */ + $mediaId = $this->insert($media, true); + + if (! $mediaId) { + $this->db->transRollback(); + return false; } @@ -150,11 +132,14 @@ class MediaModel extends Model /** * @param Document|Audio|Video|Image|Transcript|Chapters $media - * - * @noRector ReturnTypeDeclarationRector */ public function updateMedia(object $media): bool { + // save file first + // FIXME: what if file is not set? + $media->saveFile(); + + // update record in database return $this->update($media->id, $media); } @@ -173,9 +158,14 @@ class MediaModel extends Model return $result; } - public function deleteMedia(object $media): bool|BaseResult + /** + * @param Document|Audio|Video|Image|Transcript|Chapters $media + */ + public function deleteMedia($media): bool|BaseResult { - $media->deleteFile(); + if (! $media->deleteFile()) { + return false; + } return $this->delete($media->id); } diff --git a/modules/Media/TranscriptParser.php b/modules/Media/TranscriptParser.php new file mode 100644 index 00000000..67ebba2f --- /dev/null +++ b/modules/Media/TranscriptParser.php @@ -0,0 +1,232 @@ +transcriptContent = $content; + + return $this; + } + + /** + * Adapted from: https://stackoverflow.com/a/11659306 + * + * @return string Returns the json encoded string + */ + public function parseSrt(): string + { + if (! defined('SRT_STATE_SUBNUMBER')) { + define('SRT_STATE_SUBNUMBER', 0); + } + + if (! defined('SRT_STATE_TIME')) { + define('SRT_STATE_TIME', 1); + } + + if (! defined('SRT_STATE_TEXT')) { + define('SRT_STATE_TEXT', 2); + } + + if (! defined('SRT_STATE_BLANK')) { + define('SRT_STATE_BLANK', 3); + } + + $subs = []; + $state = SRT_STATE_SUBNUMBER; + $subNum = 0; + $subText = ''; + $subTime = ''; + + $lines = explode(PHP_EOL, $this->transcriptContent); + foreach ($lines as $line) { + switch ($state) { + case SRT_STATE_SUBNUMBER: + $subNum = trim($line); + $state = SRT_STATE_TIME; + break; + + case SRT_STATE_TIME: + $subTime = trim($line); + $state = SRT_STATE_TEXT; + break; + + case SRT_STATE_TEXT: + if (trim($line) === '') { + $sub = new stdClass(); + $sub->number = (int) $subNum; + [$startTime, $endTime] = explode(' --> ', $subTime); + $sub->startTime = $this->getSecondsFromTimeString($startTime); + $sub->endTime = $this->getSecondsFromTimeString($endTime); + $sub->text = trim($subText); + $subText = ''; + $state = SRT_STATE_SUBNUMBER; + $subs[] = $sub; + } elseif ($subText !== '') { + $subText .= PHP_EOL . $line; + } else { + $subText .= $line; + } + + break; + } + } + + if ($state === SRT_STATE_TEXT) { + // if file was missing the trailing newlines, we'll be in this + // state here. Append the last read text and add the last sub. + // @phpstan-ignore-next-line + $sub->text = $subText; + // @phpstan-ignore-next-line + $subs[] = $sub; + } + + $jsonString = json_encode($subs, JSON_PRETTY_PRINT); + + if (! $jsonString) { + throw new Exception('Failed to parse SRT to JSON.'); + } + + return $jsonString; + } + + public function parseVtt(): string + { + if (! defined('VTT_STATE_HEADER')) { + define('VTT_STATE_HEADER', 0); + } + + if (! defined('VTT_STATE_BLANK')) { + define('VTT_STATE_BLANK', 1); + } + + if (! defined('VTT_STATE_TIME')) { + define('VTT_STATE_TIME', 2); + } + + if (! defined('VTT_STATE_TEXT')) { + define('VTT_STATE_TEXT', 3); + } + + $subs = []; + $state = VTT_STATE_HEADER; + $subNum = 0; + $subText = ''; + $subTime = ''; + + $lines = explode(PHP_EOL, $this->transcriptContent); + // add a newline as last item, if it isn't already a newline + if (array_last($lines) !== '') { + $lines[] = PHP_EOL; + } + + foreach ($lines as $line) { + switch ($state) { + case VTT_STATE_HEADER: + $state = VTT_STATE_BLANK; + break; + + case VTT_STATE_BLANK: + $speakercount = 0; + $state = VTT_STATE_TIME; + break; + + case VTT_STATE_TIME: + $subTime = trim($line); + $state = VTT_STATE_TEXT; + break; + + case VTT_STATE_TEXT: + if (trim($line) === '') { + $state = VTT_STATE_TIME; + // @phpstan-ignore-next-line + } elseif ($subText !== '') { + $subText .= PHP_EOL . $line; + } else { + /** VTT includes a lot of information on the spoken line + * An example may look like this: + * So this is it + * We need to break this down into it's components, namely: + * 1. The actual words for the caption + * 2. Who is speaking + * 3. Any styling cues encoded in the VTT (which we dump) + * More information: https://www.w3.org/TR/webvtt1/ + * + * If there is more than one speaker in a cue, we also need + * to handle this, to repeat the start and end times for + * the second cue. + * */ + + $vtt_speaker_pattern = '/^<.*>/U'; + $removethese = ['', '<', '>']; + preg_match($vtt_speaker_pattern, $line, $matches); + if (isset($matches[0])) { + $subVoiceCue = str_replace($removethese, '', $matches[0]); + $subSpeaker = substr($subVoiceCue, strpos($subVoiceCue, ' ')); + } else { + $subSpeaker = ''; + } + + $subText .= preg_replace($vtt_speaker_pattern, '', $line); + $sub = new stdClass(); + $sub->number = $subNum; + [$startTime, $endTime] = explode(' --> ', $subTime); + $sub->startTime = $this->getSecondsFromVTTTimeString($startTime); + $sub->endTime = $this->getSecondsFromVTTTimeString($endTime); + $sub->text = trim($subText); + if ($subSpeaker !== '') { + $sub->speaker = trim($subSpeaker); + } + + $subText = ''; + $subs[] = $sub; + ++$subNum; + } + + break; + } + } + + $jsonString = json_encode($subs, JSON_PRETTY_PRINT); + + if (! $jsonString) { + throw new Exception('Failed to parse VTT to JSON.'); + } + + return $jsonString; + } + + private function getSecondsFromTimeString(string $timeString): float + { + $timeString = explode(',', $timeString); + return (strtotime($timeString[0]) - strtotime('TODAY')) + (float) "0.{$timeString[1]}"; + } + + private function getSecondsFromVTTTimeString(string $timeString): float + { + $timeString = explode('.', $timeString); + if (substr_count($timeString[0], ':') === 1) { + // add hours if only MM:SS.mmm format + $timeString[0] = '00:' . $timeString[0]; + } + + return (strtotime($timeString[0]) - strtotime('TODAY')) + (float) "0.{$timeString[1]}"; + } +} diff --git a/modules/Admin/Controllers/SchedulerController.php b/modules/MediaClipper/Commands/Generate.php similarity index 56% rename from modules/Admin/Controllers/SchedulerController.php rename to modules/MediaClipper/Commands/Generate.php index d13a29e4..656349ac 100644 --- a/modules/Admin/Controllers/SchedulerController.php +++ b/modules/MediaClipper/Commands/Generate.php @@ -2,56 +2,61 @@ declare(strict_types=1); -/** - * @copyright 2021 Ad Aures - * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 - * @link https://castopod.org/ - */ - -namespace Modules\Admin\Controllers; +namespace Modules\MediaClipper\Commands; use App\Models\ClipModel; -use CodeIgniter\Controller; +use CodeIgniter\CLI\BaseCommand; +use CodeIgniter\Files\File; use CodeIgniter\I18n\Time; use Exception; -use MediaClipper\VideoClipper; +use Modules\MediaClipper\VideoClipper; +use Override; -class SchedulerController extends Controller +class Generate extends BaseCommand { - public function generateVideoClips(): bool + protected $group = 'media-clipper'; + + protected $name = 'video-clips:generate'; + + protected $description = 'Displays basic application information.'; + + #[Override] + public function run(array $params): void { // get number of running clips to prevent from having too much running in parallel // TODO: get the number of running ffmpeg processes directly from the machine? - $runningVideoClips = (new ClipModel())->getRunningVideoClipsCount(); + $runningVideoClips = new ClipModel() + ->getRunningVideoClipsCount(); if ($runningVideoClips >= config('Admin')->videoClipWorkers) { - return true; + return; } // get all clips that haven't been processed yet - $scheduledClips = (new ClipModel())->getScheduledVideoClips(); + $scheduledClips = new ClipModel() + ->getScheduledVideoClips(); if ($scheduledClips === []) { - return true; + return; } $data = []; foreach ($scheduledClips as $scheduledClip) { $data[] = [ - 'id' => $scheduledClip->id, + 'id' => $scheduledClip->id, 'status' => 'pending', ]; } - (new ClipModel())->updateBatch($data, 'id'); + new ClipModel() + ->updateBatch($data, 'id'); // Loop through clips to generate them foreach ($scheduledClips as $scheduledClip) { try { - // set clip to pending - (new ClipModel()) + new ClipModel() ->update($scheduledClip->id, [ - 'status' => 'running', + 'status' => 'running', 'job_started_at' => Time::now(), ]); $clipper = new VideoClipper( @@ -66,32 +71,31 @@ class SchedulerController extends Controller $clipModel = new ClipModel(); if ($exitCode === 0) { // success, video was generated - $scheduledClip->setMedia($clipper->videoClipFilePath); + $scheduledClip->setMedia(new File($clipper->videoClipOutput), $clipper->videoClipFileKey); $clipModel->update($scheduledClip->id, [ - 'media_id' => $scheduledClip->media_id, - 'status' => 'passed', - 'logs' => $clipper->logs, + 'media_id' => $scheduledClip->media_id, + 'status' => 'passed', + 'logs' => $clipper->logs, 'job_ended_at' => Time::now(), ]); } else { // error $clipModel->update($scheduledClip->id, [ - 'status' => 'failed', - 'logs' => $clipper->logs, + 'status' => 'failed', + 'logs' => $clipper->logs, 'job_ended_at' => Time::now(), ]); } $clipModel->clearVideoClipCache($scheduledClip->id); } catch (Exception $exception) { - (new ClipModel())->update($scheduledClip->id, [ - 'status' => 'failed', - 'logs' => $exception, - 'job_ended_at' => Time::now(), - ]); + new ClipModel() + ->update($scheduledClip->id, [ + 'status' => 'failed', + 'logs' => $exception, + 'job_ended_at' => Time::now(), + ]); } } - - return true; } } diff --git a/modules/MediaClipper/Config/MediaClipper.php b/modules/MediaClipper/Config/MediaClipper.php new file mode 100644 index 00000000..933ed886 --- /dev/null +++ b/modules/MediaClipper/Config/MediaClipper.php @@ -0,0 +1,346 @@ +>> + */ + public array $formats = [ + 'landscape' => [ + 'width' => 1920, + 'height' => 1080, + 'cover' => [ + 'width' => 480, + 'height' => 480, + 'radius' => 24, + 'x' => 150, + 'y' => 120, + ], + 'quotes' => [ + 'width' => 192, + 'height' => 192, + 'x' => 810, + 'y' => 210, + ], + 'podcastTitle' => [ + 'fontsize' => 20, + 'x' => 150, + 'y' => 620, + 'lineWidth' => 510, + ], + 'episodeTitle' => [ + 'fontsize' => 32, + 'x' => 150, + 'y' => 660, + 'lines' => 3, + 'lineWidth' => 510, + 'lineHeight' => 1.5, + ], + 'episodeNumbering' => [ + 'fontsize' => 18, + 'paddingX' => 10, + 'paddingY' => 5, + 'marginRight' => 10, + ], + 'timestamp' => [ + 'fontsize' => 32, + 'padding' => 10, + 'x' => 1620, + 'y' => 985, + ], + 'watermark' => [ + 'width' => 90, + 'height' => 72, + 'x' => 140, + 'y' => 960, + ], + 'progressbar' => [ + 'height' => 10, + ], + 'soundwaves' => [ + 'width' => 192, + 'height' => 108, + 'rescaleWidth' => 1920, + 'rescaleHeight' => 540, + 'x' => 0, + 'y' => 810, + 'mask' => ROOTPATH . 'modules/MediaClipper/Resources/soundwaves-mask-landscape.png', + ], + 'subtitles' => [ + 'fontsize' => 18, + 'marginL' => 180, + 'marginR' => 20, + 'marginV' => 85, + ], + ], + 'portrait' => [ + 'width' => 1080, + 'height' => 1920, + 'cover' => [ + 'width' => 280, + 'height' => 280, + 'radius' => 16, + 'x' => 50, + 'y' => 50, + ], + 'quotes' => [ + 'width' => 256, + 'height' => 256, + 'x' => 40, + 'y' => 520, + ], + 'podcastTitle' => [ + 'fontsize' => 32, + 'x' => 360, + 'y' => 55, + 'lineWidth' => 670, + ], + 'episodeTitle' => [ + 'fontsize' => 42, + 'x' => 360, + 'y' => 110, + 'lines' => 3, + 'lineWidth' => 670, + 'lineHeight' => 1.5, + ], + 'episodeNumbering' => [ + 'fontsize' => 28, + 'paddingX' => 10, + 'paddingY' => 10, + 'marginRight' => 10, + ], + 'timestamp' => [ + 'fontsize' => 48, + 'padding' => 14, + 'x' => 734, + 'y' => 1800, + ], + 'watermark' => [ + 'width' => 120, + 'height' => 96, + 'x' => 130, + 'y' => 1770, + ], + 'progressbar' => [ + 'height' => 10, + ], + 'soundwaves' => [ + 'width' => 54, + 'height' => 96, + 'rescaleWidth' => 1080, + 'rescaleHeight' => 1920, + 'x' => 0, + 'y' => 960, + 'mask' => ROOTPATH . 'modules/MediaClipper/Resources/soundwaves-mask-portrait.png', + ], + 'subtitles' => [ + 'fontsize' => 16, + 'marginL' => 40, + 'marginR' => 25, + 'marginV' => 97, + ], + ], + 'squared' => [ + 'width' => 1200, + 'height' => 1200, + 'cover' => [ + 'width' => 200, + 'height' => 200, + 'radius' => 16, + 'x' => 40, + 'y' => 40, + ], + 'quotes' => [ + 'width' => 200, + 'height' => 200, + 'x' => 85, + 'y' => 320, + ], + 'podcastTitle' => [ + 'fontsize' => 28, + 'x' => 260, + 'y' => 50, + 'lines' => 1, + 'lineWidth' => 800, + ], + 'episodeTitle' => [ + 'fontsize' => 36, + 'x' => 260, + 'y' => 90, + 'lines' => 2, + 'lineWidth' => 850, + 'lineHeight' => 1.5, + ], + 'episodeNumbering' => [ + 'fontsize' => 24, + 'paddingX' => 10, + 'paddingY' => 5, + 'marginRight' => 10, + ], + 'timestamp' => [ + 'fontsize' => 48, + 'padding' => 10, + 'x' => 855, + 'y' => 1070, + ], + 'watermark' => [ + 'width' => 120, + 'height' => 96, + 'x' => 130, + 'y' => 1040, + ], + 'progressbar' => [ + 'height' => 10, + ], + 'soundwaves' => [ + 'width' => 60, + 'height' => 60, + 'rescaleWidth' => 1200, + 'rescaleHeight' => 1200, + 'x' => 0, + 'y' => 600, + 'mask' => ROOTPATH . 'modules/MediaClipper/Resources/soundwaves-mask-squared.png', + ], + 'subtitles' => [ + 'fontsize' => 20, + 'marginL' => 60, + 'marginR' => 20, + 'marginV' => 98, + ], + ], + ]; + + /** + * @var array> + */ + public array $themes = [ + 'pine' => [ + // Previews must be a HSL colorscheme string + 'preview' => '174 100% 29%', + 'preview-background' => '172 100% 17%', + // arrays are rgb + 'background' => [0, 86, 74], + 'text' => [255, 255, 255], + // subtitle hex color is BGR (Blue, Green, Red), + 'subtitles' => 'FFFFFF', + // quotes image MUST BE black + 'quotes' => [0, 148, 134], + 'episodeNumberingBg' => [0, 61, 11], + 'episodeNumberingText' => [255, 255, 255], + 'progressbar' => '009486', + 'timestampBg' => '00564A', + 'timestampText' => 'FFFFFF', + 'watermarkBg' => '00564A', + 'soundwaves' => [231, 249, 228], + ], + 'crimson' => [ + // Preview must be a HSL colorscheme string + 'preview' => '350 87% 61%', + 'preview-background' => '348 75% 40%', + // arrays are rgb + 'background' => [179, 31, 57], + 'text' => [255, 255, 255], + // subtitle hex color is BGR (Blue, Green, Red), + 'subtitles' => 'FFFFFF', + // quotes image MUST BE black + 'quotes' => [242, 70, 100], + 'episodeNumberingBg' => [152, 16, 43], + 'episodeNumberingText' => [255, 255, 255], + 'progressbar' => 'F24664', + 'timestampBg' => 'B31F39', + 'timestampText' => 'FFFFFF', + 'watermarkBg' => 'B31F39', + 'soundwaves' => [253, 206, 215], + ], + 'lake' => [ + // Preview must be a HSL colorscheme string + 'preview' => '194 100% 44%', + 'preview-background' => '194 100% 22%', + // arrays are rgb + 'background' => [0, 86, 113], + 'text' => [255, 255, 255], + // subtitle hex color is BGR (Blue, Green, Red), + 'subtitles' => 'FFFFFF', + // quotes image MUST BE black + 'quotes' => [0, 171, 225], + 'episodeNumberingBg' => [0, 43, 57], + 'episodeNumberingText' => [255, 255, 255], + 'progressbar' => '00ABE1', + 'timestampBg' => '005671', + 'timestampText' => 'FFFFFF', + 'watermarkBg' => '005671', + 'soundwaves' => [214, 245, 255], + ], + 'amber' => [ + // Preview must be a HSL colorscheme string + 'preview' => '17 100% 57%', + 'preview-background' => '17 100% 35%', + // arrays are rgb + 'background' => [177, 50, 0], + 'text' => [255, 255, 255], + // subtitle hex color is BGR (Blue, Green, Red), + 'subtitles' => 'FFFFFF', + // quotes image MUST BE black + 'quotes' => [255, 96, 34], + 'episodeNumberingBg' => [121, 34, 0], + 'episodeNumberingText' => [255, 255, 255], + 'progressbar' => 'FF6022', + 'timestampBg' => 'B13200', + 'timestampText' => 'FFFFFF', + 'watermarkBg' => 'B13200', + 'soundwaves' => [255, 213, 197], + ], + 'jacaranda' => [ + // Preview must be a HSL colorscheme string + 'preview' => '254 72% 52%', + 'preview-background' => '254 73% 30%', + // arrays are rgb + 'background' => [47, 21, 132], + 'text' => [255, 255, 255], + // subtitle hex color is BGR (Blue, Green, Red), + 'subtitles' => 'FFFFFF', + // quotes image MUST BE black + 'quotes' => [86, 45, 221], + 'episodeNumberingBg' => [30, 14, 84], + 'episodeNumberingText' => [255, 255, 255], + 'progressbar' => '562DDD', + 'timestampBg' => '2F1584', + 'timestampText' => 'FFFFFF', + 'watermarkBg' => '2F1584', + 'soundwaves' => [199, 185, 244], + ], + 'onyx' => [ + // Preview must be a HSL colorscheme string + 'preview' => '240 17% 2%', + 'preview-background' => '240 17% 2%', + // arrays are rgb + 'background' => [5, 5, 7], + 'text' => [255, 255, 255], + // subtitle hex color is BGR (Blue, Green, Red), + 'subtitles' => 'FFFFFF', + // quotes image MUST BE black + 'quotes' => [38, 38, 49], + 'episodeNumberingBg' => [0, 0, 0], + 'episodeNumberingText' => [255, 255, 255], + 'progressbar' => 'D5D5E1', + 'timestampBg' => '050507', + 'timestampText' => 'FFFFFF', + 'watermarkBg' => '050507', + 'soundwaves' => [213, 213, 225], + ], + ]; +} diff --git a/app/Libraries/MediaClipper/castopod-logo.png b/modules/MediaClipper/Resources/castopod-logo.png similarity index 100% rename from app/Libraries/MediaClipper/castopod-logo.png rename to modules/MediaClipper/Resources/castopod-logo.png diff --git a/app/Libraries/MediaClipper/fonts/Inter-Regular.otf b/modules/MediaClipper/Resources/fonts/Inter-Regular.otf similarity index 100% rename from app/Libraries/MediaClipper/fonts/Inter-Regular.otf rename to modules/MediaClipper/Resources/fonts/Inter-Regular.otf diff --git a/app/Libraries/MediaClipper/fonts/Inter-SemiBold.otf b/modules/MediaClipper/Resources/fonts/Inter-SemiBold.otf similarity index 100% rename from app/Libraries/MediaClipper/fonts/Inter-SemiBold.otf rename to modules/MediaClipper/Resources/fonts/Inter-SemiBold.otf diff --git a/app/Libraries/MediaClipper/fonts/NotoSansMono-Regular.ttf b/modules/MediaClipper/Resources/fonts/NotoSansMono-Regular.ttf similarity index 100% rename from app/Libraries/MediaClipper/fonts/NotoSansMono-Regular.ttf rename to modules/MediaClipper/Resources/fonts/NotoSansMono-Regular.ttf diff --git a/app/Libraries/MediaClipper/fonts/Rubik-Bold.ttf b/modules/MediaClipper/Resources/fonts/Rubik-Bold.ttf similarity index 100% rename from app/Libraries/MediaClipper/fonts/Rubik-Bold.ttf rename to modules/MediaClipper/Resources/fonts/Rubik-Bold.ttf diff --git a/app/Libraries/MediaClipper/quotes.png b/modules/MediaClipper/Resources/quotes.png similarity index 100% rename from app/Libraries/MediaClipper/quotes.png rename to modules/MediaClipper/Resources/quotes.png diff --git a/app/Libraries/MediaClipper/soundwaves-mask-landscape.png b/modules/MediaClipper/Resources/soundwaves-mask-landscape.png similarity index 100% rename from app/Libraries/MediaClipper/soundwaves-mask-landscape.png rename to modules/MediaClipper/Resources/soundwaves-mask-landscape.png diff --git a/app/Libraries/MediaClipper/soundwaves-mask-portrait.png b/modules/MediaClipper/Resources/soundwaves-mask-portrait.png similarity index 100% rename from app/Libraries/MediaClipper/soundwaves-mask-portrait.png rename to modules/MediaClipper/Resources/soundwaves-mask-portrait.png diff --git a/app/Libraries/MediaClipper/soundwaves-mask-squared.png b/modules/MediaClipper/Resources/soundwaves-mask-squared.png similarity index 100% rename from app/Libraries/MediaClipper/soundwaves-mask-squared.png rename to modules/MediaClipper/Resources/soundwaves-mask-squared.png diff --git a/app/Libraries/MediaClipper/VideoClipper.php b/modules/MediaClipper/VideoClipper.php similarity index 91% rename from app/Libraries/MediaClipper/VideoClipper.php rename to modules/MediaClipper/VideoClipper.php index 1c97f7cc..55e320fb 100644 --- a/app/Libraries/MediaClipper/VideoClipper.php +++ b/modules/MediaClipper/VideoClipper.php @@ -8,11 +8,13 @@ declare(strict_types=1); * @link https://castopod.org/ */ -namespace MediaClipper; +namespace Modules\MediaClipper; use App\Entities\Episode; use Exception; use GdImage; +use Modules\Media\Entities\Transcript; +use Modules\Media\FileManagers\FileManagerInterface; /** * TODO: refactor this by splitting process modules into different classes (image generation, subtitles clip, video @@ -23,21 +25,21 @@ class VideoClipper /** * @var array */ - public const FONTS = [ - 'episodeTitle' => 'Rubik-Bold.ttf', - 'podcastTitle' => 'Inter-Regular.otf', - 'subtitles' => 'Inter-SemiBold', + final public const array FONTS = [ + 'episodeTitle' => 'Rubik-Bold.ttf', + 'podcastTitle' => 'Inter-Regular.otf', + 'subtitles' => 'Inter-SemiBold', 'episodeNumbering' => 'Inter-SemiBold.otf', - 'timestamp' => 'NotoSansMono-Regular.ttf', + 'timestamp' => 'NotoSansMono-Regular.ttf', ]; public ?string $logs = null; public bool $error = false; - public string $videoClipFilePath; + public string $videoClipFileKey; - protected string $videoClipOutput; + public string $videoClipOutput; protected float $duration; @@ -83,26 +85,25 @@ class VideoClipper $this->colors = config('MediaClipper') ->themes[$theme]; - helper(['media']); + /** @var FileManagerInterface $fileManager */ + $fileManager = service('file_manager'); - $this->audioInput = media_path($this->episode->audio->file_path); - $this->episodeCoverPath = media_path($this->episode->cover->file_path); - - $podcastFolder = media_path("podcasts/{$this->episode->podcast->handle}"); - - $this->videoClipOutput = $podcastFolder . "/{$this->episode->slug}-clip-{$this->start}-to-{$this->end}-{$this->format}-{$this->theme}.mp4"; - $this->videoClipFilePath = "podcasts/{$this->episode->podcast->handle}/{$this->episode->slug}-clip-{$this->start}-to-{$this->end}-{$this->format}-{$this->theme}.mp4"; + $this->audioInput = $fileManager->getFileInput($this->episode->audio->file_key); + $this->episodeCoverPath = $fileManager->getFileInput($this->episode->cover->file_key); // Temporary files to generate clip $tempFile = tempnam(WRITEPATH . 'temp', "{$this->episode->slug}-{$this->start}-{$this->end}"); if (! $tempFile) { throw new Exception( - 'Could not create temporary files, check for permissions on your ' . WRITEPATH . 'temp folder.' + 'Could not create temporary files, check for permissions on your ' . WRITEPATH . 'temp folder.', ); } + $this->videoClipFileKey = "podcasts/{$this->episode->podcast->handle}/{$this->episode->slug}-clip-{$this->start}-to-{$this->end}-{$this->format}-{$this->theme}.mp4"; + $this->tempFileOutput = $tempFile; + $this->videoClipOutput = $tempFile . '-video-clip.mp4'; $this->soundbiteOutput = $tempFile . '-soundbite.mp3'; $this->subtitlesClipOutput = $tempFile . '-subtitle.srt'; $this->videoClipBgOutput = $tempFile . '-bg.png'; @@ -116,23 +117,26 @@ class VideoClipper public function subtitlesClip(): void { - if ($this->episode->transcript === null) { + if (! $this->episode->transcript instanceof Transcript) { throw new Exception('Episode does not have a transcript!'); } - if ($this->episode->transcript->json_path) { - $this->generateSubtitlesClipFromJson($this->episode->transcript->json_path); + if ($this->episode->transcript->json_url) { + $this->generateSubtitlesClipFromJson($this->episode->transcript->json_key); } else { - $subtitlesInput = media_path($this->episode->transcript->file_path); + $subtitlesInput = $this->episode->transcript->file_url; $subtitleClipCmd = "ffmpeg -y -i {$subtitlesInput} -ss {$this->start} -t {$this->duration} {$this->subtitlesClipOutput}"; exec($subtitleClipCmd); } } - public function generateSubtitlesClipFromJson(string $jsonFileInput): void + public function generateSubtitlesClipFromJson(string $jsonFileKey): void { - $jsonTranscriptString = file_get_contents($jsonFileInput); - if ($jsonTranscriptString === false) { + /** @var FileManagerInterface $fileManager */ + $fileManager = service('file_manager'); + + $jsonTranscriptString = (string) $fileManager->getFileContents($jsonFileKey); + if ($jsonTranscriptString === '') { throw new Exception('Cannot get transcript json contents.'); } @@ -219,7 +223,6 @@ class VideoClipper public function getCmd(): string { - // @phpstan-ignore $filters = [ "[0:a]aformat=channel_layouts=mono,showwaves=s={$this->dimensions['soundwaves']['width']}x{$this->dimensions['soundwaves']['height']}:mode=cline:rate=10:colors=white,format=rgb24[waves]", "[waves]scale={$this->dimensions['width']}:{$this->dimensions['height']}:flags=neighbor[resizedwaves]", @@ -230,11 +233,11 @@ class VideoClipper '[m][a]alphamerge[waves_t3]', "[waves_t3]scale={$this->dimensions['soundwaves']['rescaleWidth']}:{$this->dimensions['soundwaves']['rescaleHeight']},lutrgb=r='if(gt(val,100),{$this->colors['soundwaves'][0]},val)':g='if(gt(val,100),{$this->colors['soundwaves'][1]},val)':b='if(gt(val,100),{$this->colors['soundwaves'][2]},val)'[waves_final]", "[1:v][waves_final]overlay=x={$this->dimensions['soundwaves']['x']}:y={$this->dimensions['soundwaves']['y']}:shortest=1,drawtext=fontfile=" . $this->getFont( - 'timestamp' + 'timestamp', ) . ":text='%{pts\:gmtime\:{$this->start}\:%H\\\\\\\\\\:%M\\\\\\\\\\:%S\}':x={$this->dimensions['timestamp']['x']}:y={$this->dimensions['timestamp']['y']}:fontsize={$this->dimensions['timestamp']['fontsize']}:fontcolor=0x{$this->colors['timestampText']}:box=1:boxcolor=0x{$this->colors['timestampBg']}:boxborderw={$this->dimensions['timestamp']['padding']}[v3]", "color=c=0x{$this->colors['progressbar']}:s={$this->dimensions['width']}x{$this->dimensions['progressbar']['height']}[progressbar]", "[v3][progressbar]overlay=-w+(w/{$this->duration})*t:0:shortest=1:format=rgb,subtitles={$this->subtitlesClipOutput}:fontsdir=" . config( - 'MediaClipper' + 'MediaClipper', )->fontsFolder . ":force_style='Fontname=" . self::FONTS['subtitles'] . ",Alignment=5,Fontsize={$this->dimensions['subtitles']['fontsize']},PrimaryColour=&H{$this->colors['subtitles']}&,BorderStyle=1,Outline=0,Shadow=0,MarginL={$this->dimensions['subtitles']['marginL']},MarginR={$this->dimensions['subtitles']['marginR']},MarginV={$this->dimensions['subtitles']['marginV']}'[outv]", "[6:v]scale={$this->dimensions['watermark']['width']}:{$this->dimensions['watermark']['height']}[watermark]", "color=0x{$this->colors['watermarkBg']}:{$this->dimensions['watermark']['width']}x{$this->dimensions['watermark']['height']}[over]", @@ -256,6 +259,7 @@ class VideoClipper "-f lavfi -i color=white:{$this->dimensions['width']}x{$this->dimensions['height']}", "-loop 1 -framerate 1 -i {$watermark}", '-filter_complex "' . implode(';', $filters) . '"', + '-t ' . $this->duration, '-map "[outfinal]"', '-map 0:a', '-acodec aac', @@ -297,7 +301,7 @@ class VideoClipper { $background = $this->generateBackground($this->dimensions['width'], $this->dimensions['height']); - if (! $background instanceof \GdImage) { + if (! $background instanceof GdImage) { return false; } @@ -309,7 +313,7 @@ class VideoClipper $scaledEpisodeCover = $this->scaleImage( $episodeCover, $this->dimensions['cover']['width'], - $this->dimensions['cover']['height'] + $this->dimensions['cover']['height'], ); if (! $scaledEpisodeCover) { @@ -328,7 +332,7 @@ class VideoClipper $this->dimensions['cover']['x'], $this->dimensions['cover']['y'], $this->dimensions['cover']['width'], - $this->dimensions['cover']['height'] + $this->dimensions['cover']['height'], ); if (! $isOverlaid) { @@ -353,13 +357,13 @@ class VideoClipper $this->dimensions['episodeTitle']['fontsize'], 0, $this->getFont('episodeTitle'), - $this->episode->title + $this->episode->title, ); $episodeNumberingBox = $this->calculateTextBox( $this->dimensions['episodeNumbering']['fontsize'], 0, $this->getFont('episodeNumbering'), - $this->episodeNumbering + $this->episodeNumbering, ); if (! $episodeTitleBox || ! $episodeNumberingBox) { return false; @@ -415,7 +419,7 @@ class VideoClipper $scaledQuotes = $this->scaleImage( $cleanedQuotes, $this->dimensions['quotes']['width'], - $this->dimensions['quotes']['height'] + $this->dimensions['quotes']['height'], ); if (! $scaledQuotes) { @@ -429,7 +433,7 @@ class VideoClipper $this->dimensions['quotes']['x'], $this->dimensions['quotes']['y'], $this->dimensions['quotes']['width'], - $this->dimensions['quotes']['height'] + $this->dimensions['quotes']['height'], ); // Save Image @@ -501,8 +505,8 @@ class VideoClipper { return match ($this->episode->cover->file_mimetype) { 'image/jpeg' => imagecreatefromjpeg($this->episodeCoverPath), - 'image/png' => imagecreatefrompng($this->episodeCoverPath), - default => imagecreate(1400, 1400), + 'image/png' => imagecreatefrompng($this->episodeCoverPath), + default => imagecreate(1400, 1400), }; } @@ -537,9 +541,9 @@ class VideoClipper # find unique color do { - $r = rand(0, 255); - $g = rand(0, 255); - $b = rand(0, 255); + $r = random_int(0, 255); + $g = random_int(0, 255); + $b = random_int(0, 255); } while (imagecolorexact($src, $r, $g, $b) < 0); $ns = $s * $q; @@ -560,7 +564,6 @@ class VideoClipper imagefill($img, 0, 0, $alphacolor); imagecopyresampled($img, $src, 0, 0, 0, 0, $ns, $ns, $s, $s); - imagedestroy($src); imagearc($img, $radius - 1, $radius - 1, $radius * 2, $radius * 2, 180, 270, $alphacolor); imagefilltoborder($img, 0, 0, $alphacolor, $alphacolor); @@ -582,7 +585,6 @@ class VideoClipper imagealphablending($dest, false); imagefilledrectangle($dest, 0, 0, $s, $s, $alphacolor); imagecopyresampled($dest, $img, 0, 0, 0, 0, $s, $s, $ns, $ns); - imagedestroy($img); # output image imagealphablending($source, false); @@ -591,7 +593,6 @@ class VideoClipper imagecopy($source, $dest, $ws - $corner, $hs - $corner, $corner, $corner, $corner, $corner); imagecopy($source, $dest, 0, $hs - $corner, 0, $corner, $corner, $corner); imagealphablending($source, true); - imagedestroy($dest); return $source; } @@ -602,7 +603,7 @@ class VideoClipper int $x, int $y, int $width, - int $height + int $height, ): bool { return imagecopy($background, $foreground, $x, $y, 0, 0, $width, $height); } @@ -642,7 +643,7 @@ class VideoClipper $y + $fontsize + ($leading * $i), $textColor, $fontPath, - $line + $line, ); } @@ -673,11 +674,11 @@ class VideoClipper $maxY = max([$bbox[1], $bbox[3], $bbox[5], $bbox[7]]); return [ - 'left' => abs($minX) - 1, - 'top' => abs($minY), - 'width' => $maxX - $minX, + 'left' => abs($minX) - 1, + 'top' => abs($minY), + 'width' => $maxX - $minX, 'height' => $maxY - $minY, - 'box' => $bbox, + 'box' => $bbox, ]; } diff --git a/modules/Platforms/Config/Routes.php b/modules/Platforms/Config/Routes.php new file mode 100644 index 00000000..06284656 --- /dev/null +++ b/modules/Platforms/Config/Routes.php @@ -0,0 +1,64 @@ +addPlaceholder('platformType', '\bpodcasting|\bsocial|\bfunding'); + +// Admin routes for subscriptions +$routes->group( + config('Admin') + ->gateway, + [ + 'namespace' => 'Modules\Platforms\Controllers', + ], + static function ($routes): void { + $routes->group('podcasts/(:num)/platforms', static function ($routes): void { + $routes->get( + '/', + 'PlatformController::list/$1/podcasting', + [ + 'as' => 'platforms-podcasting', + 'filter' => 'permission:podcast$1.manage-platforms', + ], + ); + $routes->get( + 'social', + 'PlatformController::list/$1/social', + [ + 'as' => 'platforms-social', + 'filter' => 'permission:podcast$1.manage-platforms', + ], + ); + $routes->get( + 'funding', + 'PlatformController::list/$1/funding', + [ + 'as' => 'platforms-funding', + 'filter' => 'permission:podcast$1.manage-platforms', + ], + ); + $routes->post( + 'save/(:platformType)', + 'PlatformController::updateAction/$1/$2', + [ + 'as' => 'platforms-save', + 'filter' => 'permission:podcast$1.manage-platforms', + ], + ); + $routes->get( + '(:platformType)/(:slug)/podcast-platform-remove', + 'PlatformController::removeAction/$1/$2/$3', + [ + 'as' => 'podcast-platform-remove', + 'filter' => 'permission:podcast$1.manage-platforms', + ], + ); + }); + }, +); diff --git a/modules/Platforms/Config/Services.php b/modules/Platforms/Config/Services.php new file mode 100644 index 00000000..c9912c0c --- /dev/null +++ b/modules/Platforms/Config/Services.php @@ -0,0 +1,20 @@ +{$method}(); + } + + if ( + ! ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast + ) { + throw PageNotFoundException::forPageNotFound(); + } + + $this->podcast = $podcast; + + unset($params[0]); + return $this->{$method}(...$params); + } + + public function index(): string + { + return view('podcast/platforms/dashboard'); + } + + public function list(string $platformType): string + { + helper('form'); + + $data = [ + 'podcast' => $this->podcast, + 'platformType' => $platformType, + 'platforms' => new PlatformModel() + ->getPlatformsWithData($this->podcast->id, $platformType), + ]; + + $this->setHtmlHead(lang("Platforms.title.{$platformType}")); + replace_breadcrumb_params([ + 0 => $this->podcast->at_handle, + ]); + return view('podcast/platforms', $data); + } + + public function updateAction(string $platformType): RedirectResponse + { + $platformModel = new PlatformModel(); + $validation = service('validation'); + + $platformsData = []; + foreach ( + $this->request->getPost('platforms') as $platformSlug => $podcastPlatform + ) { + $podcastPlatformUrl = trim((string) $podcastPlatform['url']); + if ($podcastPlatformUrl === '') { + continue; + } + + if (! $validation->check(htmlentities($podcastPlatformUrl), 'valid_url_strict')) { + continue; + } + + $podcastPlatformAccountId = trim((string) $podcastPlatform['account_id']); + $platformsData[] = [ + 'podcast_id' => $this->podcast->id, + 'type' => $platformType, + 'slug' => $platformSlug, + 'link_url' => $podcastPlatformUrl, + 'account_id' => $podcastPlatformAccountId === '' ? null : $podcastPlatformAccountId, + 'is_visible' => array_key_exists('visible', $podcastPlatform) && + $podcastPlatform['visible'] === 'yes', + ]; + } + + $platformModel->savePlatforms($this->podcast->id, $platformType, $platformsData); + + return redirect() + ->back() + ->with('message', lang('Platforms.messages.updateSuccess')); + } + + public function removeAction(string $platformType, string $platformSlug): RedirectResponse + { + new PlatformModel() + ->removePlatform($this->podcast->id, $platformType, $platformSlug); + + return redirect() + ->back() + ->with('message', lang('Platforms.messages.removeLinkSuccess')); + } +} diff --git a/app/Entities/Platform.php b/modules/Platforms/Entities/Platform.php similarity index 61% rename from app/Entities/Platform.php rename to modules/Platforms/Entities/Platform.php index 8f03a58b..0f2a6710 100644 --- a/app/Entities/Platform.php +++ b/modules/Platforms/Entities/Platform.php @@ -8,20 +8,20 @@ declare(strict_types=1); * @link https://castopod.org/ */ -namespace App\Entities; +namespace Modules\Platforms\Entities; use CodeIgniter\Entity\Entity; /** + * @property int $podcast_id * @property string $slug * @property string $type * @property string $label + * @property string $link_url + * @property string|null $account_id + * @property bool $is_visible * @property string $home_url * @property string|null $submit_url - * @property string|null $link_url - * @property string|null $account_id - * @property bool|null $is_visible - * @property bool|null $is_on_embed */ class Platform extends Entity { @@ -29,14 +29,14 @@ class Platform extends Entity * @var array */ protected $casts = [ - 'slug' => 'string', - 'type' => 'string', - 'label' => 'string', - 'home_url' => 'string', - 'submit_url' => '?string', - 'link_url' => '?string', + 'podcast_id' => 'int', + 'slug' => 'string', + 'type' => 'string', + 'label' => 'string', + 'link_url' => 'string', 'account_id' => '?string', - 'is_visible' => '?boolean', - 'is_on_embed' => '?boolean', + 'is_visible' => 'boolean', + 'home_url' => 'string', + 'submit_url' => '?string', ]; } diff --git a/modules/Platforms/Models/PlatformModel.php b/modules/Platforms/Models/PlatformModel.php new file mode 100644 index 00000000..8ba8c89a --- /dev/null +++ b/modules/Platforms/Models/PlatformModel.php @@ -0,0 +1,179 @@ + + */ + protected $allowedFields = ['podcast_id', 'type', 'slug', 'link_url', 'account_id', 'is_visible']; + + /** + * @var class-string + */ + protected $returnType = Platform::class; + + /** + * @var bool + */ + protected $useSoftDeletes = false; + + /** + * @var bool + */ + protected $useTimestamps = false; + + /** + * @return Platform[] + */ + public function getPlatformsWithData(int $podcastId, string $platformType): array + { + $cacheName = "podcast#{$podcastId}_platforms_{$platformType}_withData"; + if (! ($found = cache($cacheName))) { + $platforms = service('platforms'); + + $found = $this->getPlatforms($podcastId, $platformType); + $platformsData = $platforms->getPlatformsByType($platformType); + + $knownSlugs = []; + foreach ($found as $podcastPlatform) { + $knownSlugs[] = $podcastPlatform->slug; + } + + foreach ($platformsData as $slug => $platform) { + if (! in_array($slug, $knownSlugs, true)) { + $found[] = new Platform([ + 'podcast_id' => $podcastId, + 'slug' => $slug, + 'type' => $platformType, + 'label' => $platform['label'], + 'home_url' => $platform['home_url'], + 'submit_url' => $platform['submit_url'], + 'link_url' => '', + 'account_id' => null, + 'is_visible' => false, + ]); + } + } + + cache() + ->save($cacheName, $found, DECADE); + } + + return $found; + } + + /** + * @return Platform[] + */ + public function getPlatforms(int $podcastId, string $platformType): array + { + $cacheName = "podcast#{$podcastId}_platforms_{$platformType}"; + if (! ($found = cache($cacheName))) { + $platforms = service('platforms'); + + /** @var Platform[] $found */ + $found = $this + ->where('podcast_id', $podcastId) + ->where('type', $platformType) + ->orderBy('slug') + ->findAll(); + + foreach ($found as $platform) { + $platformData = $platforms->findPlatformBySlug($platformType, $platform->slug); + + if ($platformData === null) { + // delete platform, it does not correspond to any existing one + $this->delete($platform->id); + + continue; + } + + $platform->type = $platformType; + $platform->label = $platformData['label']; + $platform->home_url = $platformData['home_url']; + $platform->submit_url = $platformData['submit_url']; + } + + cache() + ->save($cacheName, $found, DECADE); + } + + return $found; + } + + /** + * @param array> $data + * + * @return int|false Number of rows inserted or FALSE on failure + */ + public function savePlatforms(int $podcastId, string $platformType, array $data): int | false + { + $this->clearCache($podcastId); + + $platforms = service('platforms'); + + $platformsData = $platforms->getPlatformsByType($platformType); + + $this->builder() + ->whereIn('slug', array_keys($platformsData)) + ->delete(); + + if ($data === []) { + // no rows inserted + return 0; + } + + return $this->insertBatch($data); + } + + public function removePlatform(int $podcastId, string $platformType, string $platformSlug): bool | string + { + $this->clearCache($podcastId); + + return $this->builder() + ->delete([ + 'podcast_id' => $podcastId, + 'type' => $platformType, + 'slug' => $platformSlug, + ]); + } + + public function clearCache(int $podcastId): void + { + cache()->deleteMatching("podcast#{$podcastId}_platforms_*"); + + // delete localized podcast page cache + cache() + ->deleteMatching("page_podcast#{$podcastId}*"); + // delete post and episode comments pages cache + cache() + ->deleteMatching('page_post*'); + cache() + ->deleteMatching('page_episode#*'); + } +} diff --git a/modules/Platforms/Platforms.php b/modules/Platforms/Platforms.php new file mode 100644 index 00000000..13cf436c --- /dev/null +++ b/modules/Platforms/Platforms.php @@ -0,0 +1,478 @@ +> + */ + public const DATA = [ + 'podcasting' => [ + 'amazon' => [ + 'label' => 'Amazon Music', + 'home_url' => 'https://music.amazon.com/', + 'submit_url' => 'https://podcasters.amazon.com/', + ], + 'antennapod' => [ + 'label' => 'AntennaPod', + 'home_url' => 'https://antennapod.org/', + 'submit_url' => 'https://antennapod.org/documentation/podcasters-hosters/add-on-antennapod', + ], + 'anytime' => [ + 'label' => 'Anytime Podcast Player', + 'home_url' => 'https://anytimeplayer.app/', + 'submit_url' => null, + ], + 'apple' => [ + 'label' => 'Apple Podcasts', + 'home_url' => 'https://www.apple.com/itunes/podcasts/', + 'submit_url' => 'https://podcastsconnect.apple.com/my-podcasts/new-feed', + ], + 'blubrry' => [ + 'label' => 'Blubrry', + 'home_url' => 'https://www.blubrry.com/', + 'submit_url' => 'https://www.blubrry.com/addpodcast.php', + ], + 'breez' => [ + 'label' => 'Breez', + 'home_url' => 'https://breez.technology/', + 'submit_url' => null, + ], + 'castamatic' => [ + 'label' => 'Castamatic', + 'home_url' => 'https://castamatic.com/', + 'submit_url' => null, + ], + 'castbox' => [ + 'label' => 'Castbox', + 'home_url' => 'https://castbox.fm/', + 'submit_url' => 'https://helpcenter.castbox.fm/portal/kb/articles/submit-my-podcast', + ], + 'castopod' => [ + 'label' => 'Castopod', + 'home_url' => 'https://castopod.org/', + 'submit_url' => 'https://castopod.org/instances', + ], + 'castro' => [ + 'label' => 'Castro', + 'home_url' => 'http://castro.fm/', + 'submit_url' => 'https://castro.fm/support/link-to-your-podcast-in-castro', + ], + 'curiocaster' => [ + 'label' => 'CurioCaster', + 'home_url' => 'https://curiocaster.com/', + 'submit_url' => null, + ], + 'deezer' => [ + 'label' => 'Deezer', + 'home_url' => 'https://www.deezer.com/', + 'submit_url' => 'https://podcasters.deezer.com/submission', + ], + 'episodes-fm' => [ + 'label' => 'Episodes.fm', + 'home_url' => 'https://episodes.fm/', + 'submit_url' => 'https://podcastindex.org/add', + ], + 'fountain' => [ + 'label' => 'Fountain', + 'home_url' => 'https://www.fountain.fm/', + 'submit_url' => 'https://support.fountain.fm/article/56-how-to-claim-your-show-on-fountain', + ], + 'fyyd' => [ + 'label' => 'fyyd', + 'home_url' => 'https://fyyd.de/', + 'submit_url' => 'https://fyyd.de/add-feed', + ], + 'gpodder' => [ + 'label' => 'gPodder', + 'home_url' => 'https://gpodder.org/', + 'submit_url' => null, + ], + 'ivoox' => [ + 'label' => 'Ivoox', + 'home_url' => 'https://www.ivoox.com/', + 'submit_url' => null, + ], + 'listennotes' => [ + 'label' => 'ListenNotes', + 'home_url' => 'https://www.listennotes.com/', + 'submit_url' => 'https://www.listennotes.com/submit/', + ], + 'overcast' => [ + 'label' => 'Overcast', + 'home_url' => 'https://overcast.fm/', + 'submit_url' => 'https://overcast.fm/podcasterinfo', + ], + 'playerfm' => [ + 'label' => 'Player.Fm', + 'home_url' => 'https://player.fm/', + 'submit_url' => 'https://player.fm/importer/feed', + ], + 'pocketcasts' => [ + 'label' => 'Pocketcasts', + 'home_url' => 'https://www.pocketcasts.com/', + 'submit_url' => 'https://www.pocketcasts.com/submit/', + ], + 'podbean' => [ + 'label' => 'Podbean', + 'home_url' => 'https://www.podbean.com/', + 'submit_url' => 'https://www.podbean.com/site/submitPodcast', + ], + 'podcastaddict' => [ + 'label' => 'Podcast Addict', + 'home_url' => 'https://podcastaddict.com/', + 'submit_url' => 'https://podcastaddict.com/submit', + ], + 'podcastindex' => [ + 'label' => 'Podcast Index', + 'home_url' => 'https://podcastindex.org/', + 'submit_url' => 'https://podcastindex.org/add', + ], + 'podchaser' => [ + 'label' => 'Podchaser', + 'home_url' => 'https://www.podchaser.com/', + 'submit_url' => 'https://www.podchaser.com/add', + ], + 'podcloud' => [ + 'label' => 'podCloud', + 'home_url' => 'https://podcloud.fr/', + 'submit_url' => 'https://podcloud.fr/studio/podcasts/new', + ], + 'podlink' => [ + 'label' => 'pod.link', + 'home_url' => 'https://pod.link/', + 'submit_url' => null, + ], + 'podtail' => [ + 'label' => 'Podtail', + 'home_url' => 'https://podtail.com/', + 'submit_url' => 'https://podtail.com/about/faq/', + ], + 'podfriend' => [ + 'label' => 'Podfriend', + 'home_url' => 'https://www.podfriend.com/', + 'submit_url' => 'https://podcastindex.org/add', + ], + 'podverse' => [ + 'label' => 'Podverse', + 'home_url' => 'https://podverse.fm/', + 'submit_url' => 'https://docs.google.com/forms/d/e/1FAIpQLSdewKP-YrE8zGjDPrkmoJEwCxPl_gizEkmzAlTYsiWAuAk1Ng/viewform', + ], + 'radiopublic' => [ + 'label' => 'RadioPublic', + 'home_url' => 'https://radiopublic.com/', + 'submit_url' => 'https://podcasters.radiopublic.com/signup', + ], + 'spotify' => [ + 'label' => 'Spotify', + 'home_url' => 'https://www.spotify.com/', + 'submit_url' => 'https://podcasters.spotify.com/dash/submit', + ], + 'spreaker' => [ + 'label' => 'Spreaker', + 'home_url' => 'https://www.spreaker.com/', + 'submit_url' => 'https://www.spreaker.com/cms/shows/rss-import', + ], + 'tunein' => [ + 'label' => 'TuneIn', + 'home_url' => 'https://tunein.com/', + 'submit_url' => 'https://help.tunein.com/contact/add-podcast-S19TR3Sdf', + ], + 'hypercatcher' => [ + 'label' => 'HyperCatcher', + 'home_url' => 'https://hypercatcher.com/', + 'submit_url' => null, + ], + 'ivyfm' => [ + 'label' => 'Ivy.fm', + 'home_url' => 'https://ivy.fm/', + 'submit_url' => null, + ], + 'jumplink' => [ + 'label' => 'JumpLink', + 'home_url' => 'https://jump.link/', + 'submit_url' => 'https://jump.link/a/accounts/signup/', + ], + 'kasts' => [ + 'label' => 'Kasts', + 'home_url' => 'https://apps.kde.org/kasts/', + 'submit_url' => null, + ], + 'playapod' => [ + 'label' => 'Playapod', + 'home_url' => 'https://playapod.com/', + 'submit_url' => null, + ], + 'plink' => [ + 'label' => 'Plink', + 'home_url' => 'https://plinkhq.com/', + 'submit_url' => null, + ], + 'podcastchapters' => [ + 'label' => 'Podcast Chapters', + 'home_url' => 'https://chaptersapp.com/', + 'submit_url' => null, + ], + 'podcastguru' => [ + 'label' => 'Podcast Guru', + 'home_url' => 'https://podcastguru.io/', + 'submit_url' => 'https://podcastguru.io/promote-your-podcast/', + ], + 'podlp' => [ + 'label' => 'PodLP', + 'home_url' => 'https://podlp.com/', + 'submit_url' => 'https://podlp.com/submit.html', + ], + 'podnews' => [ + 'label' => 'Podnews', + 'home_url' => 'https://podnews.net/', + 'submit_url' => 'https://podnews.net/podcast/subscribe-pages', + ], + 'podstation' => [ + 'label' => 'podStation', + 'home_url' => 'https://podstation.github.io/', + 'submit_url' => null, + ], + 'sphinxchat' => [ + 'label' => 'Sphinx', + 'home_url' => 'https://sphinx.chat/', + 'submit_url' => null, + ], + 'truefans' => [ + 'label' => 'Truefans', + 'home_url' => 'https://truefans.fm/', + 'submit_url' => 'https://podcastindex.org/add', + ], + 'tsacdop' => [ + 'label' => 'Tsacdop', + 'home_url' => 'https://www.tsacdop.app/', + 'submit_url' => null, + ], + 'youtube-music' => [ + 'label' => 'YouTube Music', + 'home_url' => 'https://www.youtube.com/creators/podcasts/', + 'submit_url' => 'https://studio.youtube.com/channel/content/podcasts', + ], + ], + 'social' => [ + 'bluesky' => [ + 'label' => 'Bluesky', + 'home_url' => 'https://bsky.app/', + 'submit_url' => 'https://bsky.app/', + ], + 'discord' => [ + 'label' => 'Discord', + 'home_url' => 'https://discord.com/', + 'submit_url' => 'https://discord.com/register', + ], + 'discourse' => [ + 'label' => 'Discourse', + 'home_url' => 'https://www.discourse.org/', + 'submit_url' => null, + ], + 'facebook' => [ + 'label' => 'Facebook', + 'home_url' => 'https://www.facebook.com/', + 'submit_url' => 'https://www.facebook.com/pages/creation/', + ], + 'funkwhale' => [ + 'label' => 'Funkwhale', + 'home_url' => 'https://funkwhale.audio/', + 'submit_url' => 'https://network.funkwhale.audio/dashboards/', + ], + 'instagram' => [ + 'label' => 'Instagram', + 'home_url' => 'https://www.instagram.com/', + 'submit_url' => 'https://www.instagram.com/accounts/emailsignup/', + ], + 'linkedin' => [ + 'label' => 'LinkedIn', + 'home_url' => 'https://www.linkedin.com/', + 'submit_url' => 'https://www.linkedin.com/company/setup/new/', + ], + 'mastodon' => [ + 'label' => 'Mastodon', + 'home_url' => 'https://joinmastodon.org/', + 'submit_url' => 'https://joinmastodon.org/communities', + ], + 'matrix' => [ + 'label' => 'Matrix', + 'home_url' => 'https://matrix.org/', + 'submit_url' => 'https://matrix.org/try-matrix/', + ], + 'misskey' => [ + 'label' => 'Misskey', + 'home_url' => 'https://join.misskey.page/', + 'submit_url' => 'https://join.misskey.page/en-US/instances', + ], + 'mobilizon' => [ + 'label' => 'Mobilizon', + 'home_url' => 'https://joinmobilizon.org/', + 'submit_url' => 'https://instances.joinmobilizon.org/instances', + ], + 'peertube' => [ + 'label' => 'PeerTube', + 'home_url' => 'https://joinpeertube.org/', + 'submit_url' => 'https://joinpeertube.org/instances', + ], + 'pixelfed' => [ + 'label' => 'Pixelfed', + 'home_url' => 'https://pixelfed.org/', + 'submit_url' => 'https://beta.joinpixelfed.org/', + ], + 'pleroma' => [ + 'label' => 'Pleroma', + 'home_url' => 'https://pleroma.social/', + 'submit_url' => 'https://pleroma.social/#featured-instances', + ], + 'plume' => [ + 'label' => 'Plume', + 'home_url' => 'https://joinplu.me/', + 'submit_url' => 'https://joinplu.me/#instances', + ], + 'slack' => [ + 'label' => 'Slack', + 'home_url' => 'https://slack.com/', + 'submit_url' => 'https://slack.com/get-started#/create', + ], + 'telegram' => [ + 'label' => 'Telegram', + 'home_url' => 'https://www.telegram.org/', + 'submit_url' => null, + ], + 'threads' => [ + 'label' => 'Threads', + 'home_url' => 'https://www.threads.net/', + 'submit_url' => 'https://www.threads.net/login', + ], + 'tiktok' => [ + 'label' => 'TikTok', + 'home_url' => 'https://www.tiktok.com/', + 'submit_url' => 'https://www.tiktok.com/signup', + ], + 'twitch' => [ + 'label' => 'Twitch', + 'home_url' => 'https://www.twitch.tv/', + 'submit_url' => 'https://www.twitch.tv/signup', + ], + 'writefreely' => [ + 'label' => 'WriteFreely', + 'home_url' => 'https://writefreely.org/', + 'submit_url' => 'https://writefreely.org/instances', + ], + 'youtube' => [ + 'label' => 'YouTube', + 'home_url' => 'https://www.youtube.com/', + 'submit_url' => 'https://studio.youtube.com/', + ], + 'x' => [ + 'label' => 'Twitter / X', + 'home_url' => 'https://x.com/', + 'submit_url' => 'https://x.com/i/flow/signup', + ], + ], + 'funding' => [ + 'buymeacoffee' => [ + 'label' => 'Buy Me a Coffee', + 'home_url' => 'https://www.buymeacoffee.com/', + 'submit_url' => 'https://www.buymeacoffee.com/signup', + ], + 'paypal' => [ + 'label' => 'PayPal', + 'home_url' => 'https://www.paypal.com/', + 'submit_url' => 'https://www.paypal.com/paypalme/my/grab', + ], + 'fosspay' => [ + 'label' => 'fosspay', + 'home_url' => 'https://git.sr.ht/~sircmpwn/fosspay', + 'submit_url' => null, + ], + 'gofundme' => [ + 'label' => 'GoFundMe', + 'home_url' => 'https://www.gofundme.com/', + 'submit_url' => 'https://www.gofundme.com/sign-up', + ], + 'helloasso' => [ + 'label' => 'HelloAsso', + 'home_url' => 'https://www.helloasso.com/', + 'submit_url' => 'https://auth.helloasso.com/inscription', + ], + 'indiegogo' => [ + 'label' => 'Indiegogo', + 'home_url' => 'https://www.indiegogo.com/', + 'submit_url' => 'https://www.indiegogo.com/start-a-campaign#/', + ], + 'kickstarter' => [ + 'label' => 'Kickstarter', + 'home_url' => 'https://www.kickstarter.com/', + 'submit_url' => 'https://www.kickstarter.com/learn', + ], + 'kisskissbankbank' => [ + 'label' => 'KissKissBankBank', + 'home_url' => 'https://www.kisskissbankbank.com/', + 'submit_url' => 'https://www.kisskissbankbank.com/en/financer-mon-projet', + ], + 'kofi' => [ + 'label' => 'Ko-fi', + 'home_url' => 'https://ko-fi.com/', + 'submit_url' => 'https://ko-fi.com/account/register', + ], + 'liberapay' => [ + 'label' => 'Liberapay', + 'home_url' => 'https://liberapay.com/', + 'submit_url' => 'https://liberapay.com/sign-up', + ], + 'patreon' => [ + 'label' => 'Patreon', + 'home_url' => 'https://www.patreon.com/', + 'submit_url' => 'https://www.patreon.com/create', + ], + 'tipeee' => [ + 'label' => 'Tipeee', + 'home_url' => 'https://tipeee.com/', + 'submit_url' => 'https://tipeee.com/register/', + ], + 'ulule' => [ + 'label' => 'Ulule', + 'home_url' => 'https://www.ulule.com/', + 'submit_url' => 'https://www.ulule.com/projects/create/#/', + ], + 'donorbox' => [ + 'label' => 'Donorbox', + 'home_url' => 'https://donorbox.org/', + 'submit_url' => 'https://donorbox.org/orgs/new', + ], + ], + ]; + + /** + * @return array + */ + public function getPlatformsByType(string $type): array + { + return self::DATA[$type] ?? []; + } + + /** + * @return null|array{label:string,home_url:string,submit_url:?string} + */ + public function findPlatformBySlug(string $type, string $slug): ?array + { + $data = self::DATA[$type] ?? []; + + if (! array_key_exists($slug, $data)) { + return null; + } + + return $data[$slug]; + } +} diff --git a/modules/Plugins/Commands/Add.php b/modules/Plugins/Commands/Add.php new file mode 100644 index 00000000..97e8aa28 --- /dev/null +++ b/modules/Plugins/Commands/Add.php @@ -0,0 +1,81 @@ + + */ + protected $arguments = [ + 'plugin' => 'The pluginKey and an optional version separated by an @. If version is not provided, the latest will be added by default.', + ]; + + /** + * Actually execute a command. + * + * @param array $params + */ + #[Override] + public function run(array $params): void + { + parent::run($params); + + $plugins = $this->parsePluginsParams($params); + + /** @var PluginsManager $cpm */ + $cpm = service('cpm'); + + $cpm->install($plugins); + } + + /** + * @param array $params + * @return array + */ + private function parsePluginsParams(array $params): array + { + $plugins = []; + foreach ($params as $param) { + preg_match( + '/^(?[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9]([_.-]?[a-z0-9]+)*)(@(?\S+))?\s*$/', + $param, + $matches, + ); + + if (array_key_exists('pluginKey', $matches)) { + $plugins[$matches['pluginKey']] = $matches['version'] ?? null; + } + } + + return $plugins; + } +} diff --git a/modules/Plugins/Commands/BaseCommand.php b/modules/Plugins/Commands/BaseCommand.php new file mode 100644 index 00000000..7cc27b01 --- /dev/null +++ b/modules/Plugins/Commands/BaseCommand.php @@ -0,0 +1,46 @@ + + */ + protected $options = [ + '--debug' => 'Get log trace to follow what is happening under the hood.', + ]; + + /** + * Actually execute a command. + * + * @param array $params + * + * @return int|void + */ + public function run(array $params) + { + if (CLI::getOption('debug')) { + PluginsManagerLogger::$formatter = CpmFormatterDebug::class; + } else { + PluginsManagerLogger::$formatter = CpmFormatter::class; + } + + return 0; + } +} diff --git a/modules/Plugins/Commands/CpmFormatter.php b/modules/Plugins/Commands/CpmFormatter.php new file mode 100644 index 00000000..e312824c --- /dev/null +++ b/modules/Plugins/Commands/CpmFormatter.php @@ -0,0 +1,60 @@ + CLI::write( + sprintf('• adding plugin %s%s', CLI::color( + $log['context']['pluginKey'], + 'white', + ), $log['context']['constraint'] === null ? '' : '@' . CLI::color( + $log['context']['constraint'], + 'light_yellow', + )), + ), + 'add.end' => CLI::write( + sprintf('+ %s@%s added%s', $log['context']['pluginKey'], $log['context']['version'], PHP_EOL), + 'light_green', + ), + 'update.start' => CLI::write(sprintf('• updating plugin %s…', $log['context']['pluginKey'])), + 'update.end' => CLI::write( + '✔ ' . sprintf( + '%s updated to version %s%s', + $log['context']['pluginKey'], + $log['context']['version'], + PHP_EOL, + ), + 'light_green', + ), + 'remove.start' => CLI::write(sprintf('• removing plugin %s…', $log['context']['pluginKey'])), + 'remove.end' => CLI::write( + '- ' . sprintf('%s was removed%s', $log['context']['pluginKey'], PHP_EOL), + 'light_red', + ), + default => null, + }; + + if ($level === LogLevel::Warning) { + CLI::write('⚠️ ' . $log['message'], 'light_yellow'); + } + + if ($level === LogLevel::Error) { + CLI::newLine(); + CLI::write(' error ', 'white', 'red'); + CLI::error($log['message']); + CLI::newLine(); + + exit(1); // exit with error, something wrong happened + } + } +} diff --git a/modules/Plugins/Commands/CpmFormatterDebug.php b/modules/Plugins/Commands/CpmFormatterDebug.php new file mode 100644 index 00000000..417576b1 --- /dev/null +++ b/modules/Plugins/Commands/CpmFormatterDebug.php @@ -0,0 +1,42 @@ + CLI::write( + sprintf('%s %s', CLI::color($level->name, 'white', 'green'), json_encode( + $log, + JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT, + )), + ), + LogLevel::Warning => CLI::write( + sprintf('%s %s', CLI::color($level->name, 'white', 'yellow'), json_encode( + $log, + JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT, + )), + ), + LogLevel::Error => CLI::write( + sprintf('%s %s', CLI::color($level->name, 'white', 'red'), json_encode( + $log, + JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT, + )), + ), + default => CLI::write( + sprintf('%s %s', CLI::color($level->name, 'white', 'blue'), json_encode( + $log, + JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT, + )), + ), + }; + } +} diff --git a/modules/Plugins/Commands/CreatePlugin.php b/modules/Plugins/Commands/CreatePlugin.php new file mode 100644 index 00000000..c0bcafa0 --- /dev/null +++ b/modules/Plugins/Commands/CreatePlugin.php @@ -0,0 +1,153 @@ + ['use App\Entities\Podcast;'], + 'rssAfterChannel' => ['use App\Entities\Podcast;', 'use App\Libraries\RssFeed;'], + 'rssBeforeItem' => ['use App\Entities\Episode;'], + 'rssAfterItem' => ['use App\Entities\Episode;', 'use App\Libraries\RssFeed;'], + 'siteHead' => ['use use App\Libraries\HtmlHead'], + ]; + + protected const HOOKS_METHODS = [ + 'rssBeforeChannel' => ' public function rssBeforeChannel(Podcast $podcast): void + { + // YOUR CODE HERE + }', + 'rssAfterChannel' => ' public function rssAfterChannel(Podcast $podcast, RssFeed $channel): void + { + // YOUR CODE HERE + }', + 'rssBeforeItem' => ' public function rssBeforeItem(Episode $episode): void + { + // YOUR CODE HERE + }', + 'rssAfterItem' => ' public function rssAfterItem(Episode $episode, RssFeed $item): void + { + // YOUR CODE HERE + }', + 'siteHead' => ' public function siteHead(HtmlHead $head): void + { + // YOUR CODE HERE + }', + ]; + + /** + * @var string + */ + protected $name = 'plugins:create'; + + /** + * @var string + */ + protected $description = 'Generates a new plugin folder based on a template.'; + + /** + * Actually execute a command. + * + * @param list $params + */ + #[Override] + public function run(array $params): void + { + $pluginName = CLI::prompt( + 'Plugin name (/)', + 'acme/hello-world', + Manifest::$validation_rules['name'], + ); + CLI::newLine(); + $description = CLI::prompt('Description', '', Manifest::$validation_rules['description']); + CLI::newLine(); + $license = CLI::prompt('License', 'UNLICENSED', Manifest::$validation_rules['license']); + CLI::newLine(); + $hooks = CLI::promptByMultipleKeys('Which hooks do you want to implement?', Plugins::HOOKS); + + $nameParts = explode('/', $pluginName); + $vendor = $nameParts[0]; + $name = $nameParts[1]; + + /** @var PluginsConfig $pluginsConfig */ + $pluginsConfig = config('Plugins'); + + // 1. create plugin directory if not existent + $pluginDirectory = $pluginsConfig->folder . $vendor . DIRECTORY_SEPARATOR . $name; + if (! file_exists($pluginDirectory)) { + mkdir($pluginDirectory, 0755, true); + } + + // 2. get contents of templates + $manifestTemplate = file_get_contents(__DIR__ . '/plugin-template/manifest.tpl.json'); + + if (! $manifestTemplate) { + throw new Exception('Failed to get manifest template.'); + } + + $pluginClassTemplate = file_get_contents(__DIR__ . '/plugin-template/Plugin.tpl.php'); + + if (! $pluginClassTemplate) { + throw new Exception('Failed to get Plugin class template.'); + } + + // 3. edit templates' contents + $manifestContents = str_replace('"name": ""', '"name": "' . $pluginName . '"', $manifestTemplate); + $manifestContents = str_replace( + '"description": ""', + '"description": "' . $description . '"', + $manifestContents, + ); + $manifestContents = str_replace('"license": ""', '"license": "' . $license . '"', $manifestContents); + $manifestContents = str_replace( + '"hooks": []', + '"hooks": ["' . implode('", "', $hooks) . '"]', + $manifestContents, + ); + + $pluginClassName = str_replace( + ' ', + '', + ucwords(str_replace(['-', '_', '.'], ' ', $vendor . ' ' . $name)) . 'Plugin', + ); + $pluginClassContents = str_replace('class Plugin', 'class ' . $pluginClassName, $pluginClassTemplate); + + $allImports = []; + $allMethods = []; + foreach ($hooks as $hook) { + $allImports = [...$allImports, ...self::HOOKS_IMPORTS[$hook]]; + $allMethods = [...$allMethods, self::HOOKS_METHODS[$hook]]; + } + + $imports = implode(PHP_EOL, array_unique($allImports)); + $methods = implode(PHP_EOL . PHP_EOL, $allMethods); + $pluginClassContents = str_replace('// IMPORTS_HERE', $imports, $pluginClassContents); + $pluginClassContents = str_replace(' // HOOKS_HERE', $methods, $pluginClassContents); + + $manifest = $pluginDirectory . '/manifest.json'; + $pluginClass = $pluginDirectory . '/Plugin.php'; + + if (! file_put_contents($manifest, $manifestContents)) { + throw new Exception('Failed to create manifest.json file.'); + } + + if (! file_put_contents($pluginClass, $pluginClassContents)) { + throw new Exception('Failed to create Plugin class file.'); + } + + CLI::newLine(1); + CLI::write( + sprintf('Plugin %s created in %s', CLI::color($pluginName, 'white'), CLI::color($pluginDirectory, 'white')), + 'green', + ); + } +} diff --git a/modules/Plugins/Commands/Install.php b/modules/Plugins/Commands/Install.php new file mode 100644 index 00000000..11c80a03 --- /dev/null +++ b/modules/Plugins/Commands/Install.php @@ -0,0 +1,48 @@ + $params + */ + #[Override] + public function run(array $params): void + { + parent::run($params); + + /** @var PluginsManager $cpm */ + $cpm = service('cpm'); + + $cpm->installFromPluginsTxt(); + } +} diff --git a/modules/Plugins/Commands/Remove.php b/modules/Plugins/Commands/Remove.php new file mode 100644 index 00000000..6f54212f --- /dev/null +++ b/modules/Plugins/Commands/Remove.php @@ -0,0 +1,85 @@ + + */ + protected $arguments = [ + 'plugins' => 'One or more plugins as vendor/plugin', + ]; + + /** + * @param list $params + */ + #[Override] + public function run(array $params): int + { + parent::run($params); + + /** @var Plugins $plugins */ + $plugins = service('plugins'); + + /** @var PluginsManager $cpm */ + $cpm = service('cpm'); + + /** @var list $errors */ + $errors = []; + foreach ($params as $pluginKey) { + $plugin = $plugins->getPluginByKey($pluginKey); + + if ($plugin === null) { + $errors[] = sprintf('Plugin %s was not found.', $pluginKey); + continue; + } + + if (! $plugins->uninstall($plugin)) { + $errors[] = sprintf('Something happened when removing %s', $pluginKey); + break; + } + + // delete plugin folder + $cpm->remove($pluginKey); + } + + foreach ($errors as $error) { + CLI::write(' error ', 'white', 'red'); + CLI::error($error); + CLI::newLine(); + } + + return $errors === [] ? 0 : 1; + } +} diff --git a/modules/Plugins/Commands/Update.php b/modules/Plugins/Commands/Update.php new file mode 100644 index 00000000..08836bfe --- /dev/null +++ b/modules/Plugins/Commands/Update.php @@ -0,0 +1,65 @@ + + */ + protected $arguments = [ + 'plugin' => 'The pluginKey and an optional version separated by an @. If version is not provided, the latest will be added by default.', + ]; + + /** + * Actually execute a command. + * + * @param array $params + * + * @return int|void + */ + #[Override] + public function run(array $params) + { + parent::run($params); + + if ($params === []) { + CLI::error('Missing pluginKey argument.'); + return 1; + } + + /** @var PluginsManager $cpm */ + $cpm = service('cpm'); + + $cpm->update($params[0]); + } +} diff --git a/modules/Plugins/Commands/plugin-template/Plugin.tpl.php b/modules/Plugins/Commands/plugin-template/Plugin.tpl.php new file mode 100644 index 00000000..edf615e5 --- /dev/null +++ b/modules/Plugins/Commands/plugin-template/Plugin.tpl.php @@ -0,0 +1,11 @@ +group( + config('Admin') + ->gateway, + [ + 'namespace' => 'Modules\Plugins\Controllers', + ], + static function ($routes): void { + $routes->group('plugins', static function ($routes): void { + $routes->get('/', 'PluginController::installed', [ + 'as' => 'plugins-installed', + 'filter' => 'permission:plugins.manage', + ]); + $routes->get('(:segment)', 'PluginController::vendor/$1', [ + 'as' => 'plugins-vendor', + 'filter' => 'permission:plugins.manage', + ]); + $routes->group('(:segment)/(:segment)', static function ($routes): void { + $routes->get('/', 'PluginController::view/$1/$2', [ + 'as' => 'plugins-view', + 'filter' => 'permission:plugins.manage', + ]); + $routes->get('settings', 'PluginController::settingsView/$1/$2', [ + 'as' => 'plugins-settings-general', + 'filter' => 'permission:plugins.manage', + ]); + $routes->post('settings', 'PluginController::settingsAction/$1/$2', [ + 'as' => 'plugins-settings-general-action', + 'filter' => 'permission:plugins.manage', + ]); + $routes->get('(:num)', 'PluginController::settingsView/$1/$2/$3', [ + 'as' => 'plugins-settings-podcast', + 'filter' => 'permission:podcast$3.edit', + ]); + $routes->post('(:num)', 'PluginController::settingsAction/$1/$2/$3', [ + 'as' => 'plugins-settings-podcast-action', + 'filter' => 'permission:podcast$3.edit', + ]); + $routes->get('(:num)/(:num)', 'PluginController::settingsView/$1/$2/$3/$4', [ + 'as' => 'plugins-settings-episode', + 'filter' => 'permission:podcast$3.episodes.edit', + ]); + $routes->post('(:num)/(:num)', 'PluginController::settingsAction/$1/$2/$3/$4', [ + 'as' => 'plugins-settings-episode-action', + 'filter' => 'permission:podcast$3.episodes.edit', + ]); + $routes->post('activate', 'PluginController::activate/$1/$2', [ + 'as' => 'plugins-activate', + 'filter' => 'permission:plugins.manage', + ]); + $routes->post('deactivate', 'PluginController::deactivate/$1/$2', [ + 'as' => 'plugins-deactivate', + 'filter' => 'permission:plugins.manage', + ]); + $routes->get('uninstall', 'PluginController::uninstall/$1/$2', [ + 'as' => 'plugins-uninstall', + 'filter' => 'permission:plugins.manage', + ]); + }); + }); + }, +); diff --git a/modules/Plugins/Config/Services.php b/modules/Plugins/Config/Services.php new file mode 100644 index 00000000..ea91b4d7 --- /dev/null +++ b/modules/Plugins/Config/Services.php @@ -0,0 +1,37 @@ +repositoryUrl, WRITEPATH, $config->folder, WRITEPATH . 'temp'); + } +} diff --git a/modules/Plugins/Controllers/PluginController.php b/modules/Plugins/Controllers/PluginController.php new file mode 100644 index 00000000..2028cdec --- /dev/null +++ b/modules/Plugins/Controllers/PluginController.php @@ -0,0 +1,362 @@ +plugins = service('plugins'); + } + + public function installed(): string + { + $pager = service('pager'); + + $page = (int) ($this->request->getGet('page') ?? 1); + $perPage = 10; + $total = $this->plugins->getInstalledCount(); + + $pager_links = $pager->makeLinks($page, $perPage, $total); + + $this->setHtmlHead(lang('Plugins.installed')); + return view('plugins/installed', [ + 'total' => $total, + 'plugins' => $this->plugins->getPlugins($page, $perPage), + 'pager_links' => $pager_links, + ]); + } + + public function vendor(string $vendor): string + { + $vendorPlugins = $this->plugins->getVendorPlugins($vendor); + + $this->setHtmlHead(lang('Plugins.installed')); + replace_breadcrumb_params([ + $vendor => $vendor, + ]); + return view('plugins/installed', [ + 'total' => count($vendorPlugins), + 'plugins' => $vendorPlugins, + 'pager_links' => '', + ]); + } + + public function view(string $vendor, string $package): string + { + + $plugin = $this->plugins->getPlugin($vendor, $package); + + if (! $plugin instanceof BasePlugin) { + throw PageNotFoundException::forPageNotFound(); + } + + $this->setHtmlHead($plugin->getTitle()); + replace_breadcrumb_params([ + $vendor => $vendor, + $package => $package, + ]); + return view('plugins/view', [ + 'plugin' => $plugin, + ]); + } + + public function settingsView( + string $vendor, + string $package, + ?string $podcastId = null, + ?string $episodeId = null, + ): string { + + $plugin = $this->plugins->getPlugin($vendor, $package); + + if (! $plugin instanceof BasePlugin) { + throw PageNotFoundException::forPageNotFound(); + } + + $type = 'general'; + $context = null; + $breadcrumbReplacements = [ + $vendor => $vendor, + $package => $package, + ]; + $data = [ + 'plugin' => $plugin, + ]; + + if ($podcastId !== null) { + $podcast = new PodcastModel() + ->getPodcastById((int) $podcastId); + + if (! $podcast instanceof Podcast) { + throw PageNotFoundException::forPageNotFound(); + } + + $type = 'podcast'; + $context = ['podcast', (int) $podcastId]; + $breadcrumbReplacements[0] = $podcast->handle; + $data['podcast'] = $podcast; + } + + if ($episodeId !== null) { + $episode = new EpisodeModel() + ->getEpisodeById((int) $episodeId); + + if (! $episode instanceof Episode) { + throw PageNotFoundException::forPageNotFound(); + } + + $type = 'episode'; + $context = ['episode', (int) $episodeId]; + $breadcrumbReplacements[1] = $episode->title; + $data['episode'] = $episode; + } + + $fields = $plugin->getSettingsFields($type); + + if ($fields === []) { + throw PageNotFoundException::forPageNotFound(); + } + + $data['type'] = $type; + $data['context'] = $context; + $data['fields'] = $fields; + + helper('form'); + $this->setHtmlHead(lang('Plugins.settingsTitle', [ + 'pluginTitle' => $plugin->getTitle(), + 'type' => $type, + ])); + replace_breadcrumb_params($breadcrumbReplacements); + return view('plugins/settings', $data); + } + + public function settingsAction( + string $vendor, + string $package, + ?string $podcastId = null, + ?string $episodeId = null, + ): RedirectResponse { + $plugin = $this->plugins->getPlugin($vendor, $package); + + if (! $plugin instanceof BasePlugin) { + throw PageNotFoundException::forPageNotFound(); + } + + $type = 'general'; + $context = null; + if ($podcastId !== null) { + $type = 'podcast'; + $context = ['podcast', (int) $podcastId]; + } + + if ($episodeId !== null) { + $type = 'episode'; + $context = ['episode', (int) $episodeId]; + } + + // construct validation rules first + $rules = []; + foreach ($plugin->getSettingsFields($type) as $field) { + $typeRules = $this->plugins::FIELDS_VALIDATIONS[$field->type]; + if (! in_array('permit_empty', $typeRules, true)) { + $typeRules[] = $field->optional ? 'permit_empty' : 'required'; + } + + if ($field->multiple) { + if ($field instanceof Group) { + foreach ($field->fields as $subField) { + $typeRules = $this->plugins::FIELDS_VALIDATIONS[$subField->type]; + if (! in_array('permit_empty', $typeRules, true)) { + $typeRules[] = $subField->optional ? 'permit_empty' : 'required'; + } + + $rules[sprintf('%s.*.%s', $field->key, $subField->key)] = [ + ...$typeRules, + ...$subField->validationRules, + ]; + } + } else { + $rules[$field->key . '.*'] = [...$typeRules, ...$field->validationRules]; + } + } elseif ($field instanceof Group) { + foreach ($field->fields as $subField) { + $typeRules = $this->plugins::FIELDS_VALIDATIONS[$subField->type]; + if (! in_array('permit_empty', $typeRules, true)) { + $typeRules[] = $subField->optional ? 'permit_empty' : 'required'; + } + + $rules[sprintf('%s.%s', $field->key, $subField->key)] = [ + ...$typeRules, + ...$subField->validationRules, + ]; + } + } else { + $rules[$field->key] = [...$typeRules, ...$field->validationRules]; + } + } + + if (! $this->validate($rules)) { + return redirect() + ->back() + ->withInput() + ->with('errors', $this->validator->getErrors()); + } + + $validatedData = $this->validator->getValidated(); + + foreach ($plugin->getSettingsFields($type) as $field) { + $fieldValue = $validatedData[$field->key] ?? null; + + $this->plugins->setOption($plugin, $field->key, $this->castFieldValue($field, $fieldValue), $context); + } + + // clear cache after setting options + $plugin->clearCache(); + + return redirect()->back() + ->with('message', lang('Plugins.messages.saveSettingsSuccess', [ + 'pluginTitle' => $plugin->getTitle(), + ])); + } + + public function activate(string $vendor, string $package): RedirectResponse + { + + $plugin = $this->plugins->getPlugin($vendor, $package); + + if (! $plugin instanceof BasePlugin) { + throw PageNotFoundException::forPageNotFound(); + } + + $this->plugins->activate($plugin); + + // clear cache after activation + $plugin->clearCache(); + + return redirect()->back(); + } + + public function deactivate(string $vendor, string $package): RedirectResponse + { + + $plugin = $this->plugins->getPlugin($vendor, $package); + + if (! $plugin instanceof BasePlugin) { + throw PageNotFoundException::forPageNotFound(); + } + + $this->plugins->deactivate($plugin); + + // clear cache after deactivation + $plugin->clearCache(); + + return redirect()->back(); + } + + public function uninstall(string $vendor, string $package): RedirectResponse + { + + $plugin = $this->plugins->getPlugin($vendor, $package); + + if (! $plugin instanceof BasePlugin) { + throw PageNotFoundException::forPageNotFound(); + } + + $this->plugins->uninstall($plugin); + + return redirect()->back(); + } + + private function castFieldValue(Field $field, mixed $fieldValue): mixed + { + if ($fieldValue === '' || $fieldValue === null) { + return null; + } + + $value = null; + if ($field->multiple) { + $value = []; + foreach ($fieldValue as $key => $val) { + if ($val === '') { + continue; + } + + if ($field instanceof Group) { + foreach ($val as $subKey => $subVal) { + /** @var Field|false $subField */ + $subField = array_column($field->fields, null, 'key')[$subKey] ?? false; + if (! $subField) { + continue; + } + + $v = $this->castValue($subVal, $subField->type); + if ($v) { + $value[$key][$subKey] = $v; + } + } + } else { + $value[$key] = $this->castValue($val, $field->type); + } + } + } elseif ($field instanceof Group) { + foreach ($fieldValue as $subKey => $subVal) { + /** @var Field|false $subField */ + $subField = array_column($field->fields, null, 'key')[$subKey] ?? false; + if (! $subField) { + continue; + } + + $v = $this->castValue($subVal, $subField->type); + if ($v) { + $value[$subKey] = $v; + } + } + } else { + $value = $this->castValue($fieldValue, $field->type); + } + + return $value === [] ? null : $value; + } + + private function castValue(mixed $value, string $type): mixed + { + if ($value === '' || $value === null) { + return null; + } + + return match ($this->plugins::FIELDS_CASTS[$type] ?? 'text') { + 'bool' => $value === 'yes', + 'int' => (int) $value, + 'uri' => new URI($value), + 'datetime' => Time::createFromFormat( + 'Y-m-d H:i', + $value, + $this->request->getPost('client_timezone'), + )->setTimezone(app_timezone()), + 'markdown' => new Markdown($value), + 'rss' => new RSS($value), + default => $value, + }; + } +} diff --git a/modules/Plugins/Core/BasePlugin.php b/modules/Plugins/Core/BasePlugin.php new file mode 100644 index 00000000..995fa272 --- /dev/null +++ b/modules/Plugins/Core/BasePlugin.php @@ -0,0 +1,389 @@ +key = sprintf('%s/%s', $vendor, $package); + + // TODO: cache manifest data + $manifestPath = $directory . '/manifest.json'; + + $this->manifest = new Manifest($this->key); + $this->manifest->loadFromFile($manifestPath); + + // check compatibility with Castopod version + if ($this->manifest->minCastopodVersion !== null && version_compare( + CP_VERSION, + $this->manifest->minCastopodVersion, + '<', + )) { + $this->status = PluginStatus::INCOMPATIBLE; + } else { + $this->status = get_plugin_setting($this->key, 'active') ? PluginStatus::ACTIVE : PluginStatus::INACTIVE; + } + + $this->iconSrc = $this->loadIcon($directory . '/icon.svg'); + + $this->readmeHTML = $this->loadMarkdownAsHTML($directory . '/README.md'); + + $this->licenseHTML = $this->loadMarkdownAsHTML($directory . '/LICENSE.md'); + } + + /** + * @param list|string $value + */ + public function __set(string $name, array|string $value): void + { + $this->{$name} = $value; + } + + #[Override] + public function rssBeforeChannel(Podcast $podcast): void + { + } + + #[Override] + public function rssAfterChannel(Podcast $podcast, RssFeed $channel): void + { + } + + #[Override] + public function rssBeforeItem(Episode $episode): void + { + } + + #[Override] + public function rssAfterItem(Episode $episode, RssFeed $item): void + { + } + + #[Override] + public function siteHead(HtmlHead $head): void + { + } + + final public function getGeneralSetting(string $key): mixed + { + return get_plugin_setting($this->key, $key); + } + + final public function getPodcastSetting(int $podcastId, string $key): mixed + { + return get_plugin_setting($this->key, $key, ['podcast', $podcastId]); + } + + final public function getEpisodeSetting(int $episodeId, string $key): mixed + { + return get_plugin_setting($this->key, $key, ['episode', $episodeId]); + } + + final public function clearCache(): void + { + foreach ($this->getHooks() as $hook) { + foreach (Plugins::CACHE_MAP[$hook] ?? [] as $cacheGlob) { + cache()->deleteMatching($cacheGlob); + } + } + } + + /** + * @return bool true on success, false on failure + */ + final public function activate(): bool + { + if ($this->status === PluginStatus::ACTIVE) { + return false; + } + + $this->setStatus(PluginStatus::ACTIVE); + + if ($this->status === PluginStatus::INACTIVE) { + return false; + } + + set_plugin_setting($this->key, 'active', true); + return true; + } + + final public function deactivate(): bool + { + if ($this->status !== PluginStatus::ACTIVE) { + return false; + } + + $this->setStatus(PluginStatus::INACTIVE); + set_plugin_setting($this->key, 'active', false); + + return true; + } + + final public function getStatus(): PluginStatus + { + return $this->status; + } + + final public function getDirectory(): string + { + return $this->directory; + } + + /** + * @return array + */ + final public function getManifestErrors(): array + { + return Manifest::getPluginErrors($this->key); + } + + final public function isHookDeclared(string $name): bool + { + return in_array($name, $this->manifest->hooks, true); + } + + final public function getVersion(): string + { + return $this->manifest->version; + } + + final public function getHomepage(): ?URI + { + return $this->manifest->homepage; + } + + final public function getRepository(): ?Repository + { + return $this->manifest->repository; + } + + /** + * @return list + */ + final public function getKeywords(): array + { + return $this->manifest->keywords; + } + + /** + * @return Person[] + */ + final public function getAuthors(): array + { + return $this->manifest->authors; + } + + final public function getIconSrc(): string + { + return $this->iconSrc; + } + + /** + * @return Field[] + */ + final public function getSettingsFields(string $type): array + { + $settings = $this->manifest->settings; + if (! $settings instanceof Settings) { + return []; + } + + return $settings->{$type}; + } + + final public function getMinCastopodVersion(): string + { + return $this->manifest->minCastopodVersion ?? ''; + } + + /** + * @return list + */ + final public function getHooks(): array + { + return $this->manifest->hooks; + } + + final public function getKey(): string + { + return $this->key; + } + + final public function getVendor(): string + { + return $this->vendor; + } + + final public function getPackage(): string + { + return $this->package; + } + + final public function getTitle(): string + { + $key = sprintf('Plugin.%s.title', $this->key); + /** @var string $title */ + $title = lang($key); + + if ($title === $key) { + return $this->manifest->name; + } + + return $title; + } + + final public function getDescription(): string + { + $key = sprintf('Plugin.%s.description', $this->key); + + /** @var string $description */ + $description = lang($key); + + if ($description === $key) { + return esc($this->manifest->description); + } + + return $description; + } + + final public function getReadmeHTML(): ?string + { + return $this->readmeHTML; + } + + final public function getLicense(): string + { + return $this->manifest->license ?? 'UNLICENSED'; + } + + final public function getLicenseHTML(): ?string + { + return $this->licenseHTML; + } + + /** + * @param PluginStatus::ACTIVE|PluginStatus::INACTIVE $value + */ + final protected function setStatus(PluginStatus $value): self + { + if ($this->getManifestErrors() !== []) { + $this->status = PluginStatus::INVALID; + + return $this; + } + + $this->status = $value; + + return $this; + } + + final protected function getOption(string $option): mixed + { + return get_plugin_setting($this->key, $option); + } + + final protected function setOption(string $option, mixed $value = null): void + { + set_plugin_setting($this->key, $option, $value); + } + + private function loadIcon(string $path): string + { + // TODO: cache icon + $svgIcon = @file_get_contents($path); + + if (! $svgIcon) { + return "data:image/svg+xml;utf8,%3Csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 64 64'%3E%3Cpath fill='%2300564A' d='M0 0h64v64H0z'%2F%3E%3Cpath fill='%23E7F9E4' d='M25.3 18.7a5 5 0 1 1 9.7 1.6h7c1 0 1.7.8 1.7 1.7v7a5 5 0 1 1 0 9.4v7c0 .9-.8 1.6-1.7 1.6H18.7c-1 0-1.7-.7-1.7-1.7V22c0-1 .7-1.7 1.7-1.7h7a5 5 0 0 1-.4-1.6Z'%2F%3E%3C%2Fsvg%3E"; + } + + $encodedIcon = rawurlencode(str_replace(["\r", "\n"], ' ', $svgIcon)); + return 'data:image/svg+xml;utf8,' . str_replace( + ['%20', '%22', '%27', '%3D'], + [' ', "'", "'", '='], + $encodedIcon, + ); + } + + private function loadMarkdownAsHTML(string $path): ?string + { + // TODO: cache readme + $readmeMD = @file_get_contents($path); + + if (! $readmeMD) { + return null; + } + + $environment = new Environment([ + 'html_input' => 'escape', + 'allow_unsafe_links' => false, + 'host' => new URI(base_url()) + ->getHost(), + ]); + $environment->addExtension(new CommonMarkCoreExtension()); + + $environment->addExtension(new GithubFlavoredMarkdownExtension()); + $environment->addExtension(new SmartPunctExtension()); + + $environment->addEventListener( + DocumentParsedEvent::class, + static function (DocumentParsedEvent $event): void { + new ExternalLinkProcessor() + ->onDocumentParsed($event); + }, + ); + $environment->addEventListener( + DocumentParsedEvent::class, + static function (DocumentParsedEvent $event): void { + new ExternalImageProcessor() + ->onDocumentParsed($event); + }, + ); + + $converter = new MarkdownConverter($environment); + + return $converter->convert($readmeMD) + ->getContent(); + } +} diff --git a/modules/Plugins/Core/Markdown.php b/modules/Plugins/Core/Markdown.php new file mode 100644 index 00000000..2e6b1af7 --- /dev/null +++ b/modules/Plugins/Core/Markdown.php @@ -0,0 +1,46 @@ +markdown; + } + + public function renderHTML(): string + { + $config = [ + 'html_input' => 'escape', + 'allow_unsafe_links' => false, + ]; + + $environment = new Environment($config); + $environment->addExtension(new CommonMarkCoreExtension()); + $environment->addExtension(new AutolinkExtension()); + $environment->addExtension(new SmartPunctExtension()); + $environment->addExtension(new DisallowedRawHtmlExtension()); + + $converter = new MarkdownConverter($environment); + + return (string) $converter->convert($this->markdown); + } +} diff --git a/modules/Plugins/Core/PluginInterface.php b/modules/Plugins/Core/PluginInterface.php new file mode 100644 index 00000000..bd30aad5 --- /dev/null +++ b/modules/Plugins/Core/PluginInterface.php @@ -0,0 +1,23 @@ + + */ + public const HOOKS = ['rssBeforeChannel', 'rssAfterChannel', 'rssBeforeItem', 'rssAfterItem', 'siteHead']; + + public const CACHE_MAP = [ + 'rssBeforeChannel' => ['podcast*feed*'], + 'rssAfterChannel' => ['podcast*feed*'], + 'rssBeforeItem' => ['podcast*feed*'], + 'rssAfterItem' => ['podcast*feed*'], + 'siteHead' => ['page*'], + ]; + + public const FIELDS_VALIDATIONS = [ + 'checkbox' => ['permit_empty'], + 'datetime' => ['valid_date[Y-m-d H:i]'], + 'email' => ['valid_email'], + 'group' => ['permit_empty', 'is_list'], + 'html' => ['string'], + 'markdown' => ['string'], + 'number' => ['integer'], + 'radio-group' => ['string'], + 'rss' => ['string'], + 'select' => ['string'], + 'select-multiple' => ['permit_empty', 'is_list'], + 'text' => ['string'], + 'textarea' => ['string'], + 'toggler' => ['permit_empty'], + 'url' => ['valid_url_strict'], + ]; + + public const FIELDS_CASTS = [ + 'checkbox' => 'bool', + 'datetime' => 'datetime', + 'markdown' => 'markdown', + 'number' => 'int', + 'rss' => 'rss', + 'toggler' => 'bool', + 'url' => 'uri', + ]; + + /** + * @var array + */ + protected static array $plugins = []; + + /** + * @var array + */ + protected static array $pluginsByVendor = []; + + protected static int $installedCount = 0; + + protected static int $activeCount = 0; + + public function __construct( + protected PluginsConfig $config, + ) { + helper('plugins'); + + $this->registerPlugins(); + } + + /** + * @param value-of $name + * @param array $arguments + */ + public function __call(string $name, array $arguments): void + { + if (! in_array($name, static::HOOKS, true)) { + return; + } + + $this->runHook($name, $arguments); + } + + /** + * @return array + */ + public function getPlugins(int $page = 1, int $perPage = 12): array + { + return array_slice(static::$plugins, (($page - 1) * $perPage), $perPage); + } + + /** + * @return array + */ + public function getAllPlugins(): array + { + return static::$plugins; + } + + /** + * @return array + */ + public function getActivePlugins(): array + { + $activePlugins = []; + foreach (static::$plugins as $plugin) { + if ($plugin->getStatus() === PluginStatus::ACTIVE) { + $activePlugins[] = $plugin; + } + } + + return $activePlugins; + } + + /** + * @return array + */ + public function getPluginsWithPodcastSettings(): array + { + $pluginsWithPodcastSettings = []; + foreach (static::$plugins as $plugin) { + if ($plugin->getStatus() !== PluginStatus::ACTIVE) { + continue; + } + + if ($plugin->getSettingsFields('podcast') === []) { + continue; + } + + $pluginsWithPodcastSettings[] = $plugin; + } + + return $pluginsWithPodcastSettings; + } + + /** + * @return array + */ + public function getPluginsWithEpisodeSettings(): array + { + $pluginsWithEpisodeSettings = []; + foreach (static::$plugins as $plugin) { + if ($plugin->getStatus() !== PluginStatus::ACTIVE) { + continue; + } + + if ($plugin->getSettingsFields('episode') === []) { + continue; + } + + $pluginsWithEpisodeSettings[] = $plugin; + } + + return $pluginsWithEpisodeSettings; + } + + /** + * @return array + */ + public function getVendorPlugins(string $vendor): array + { + return static::$pluginsByVendor[$vendor] ?? []; + } + + public function getPlugin(string $vendor, string $package): ?BasePlugin + { + foreach ($this->getVendorPlugins($vendor) as $plugin) { + if ($plugin->getPackage() === $package) { + return $plugin; + } + } + + return null; + } + + public function getPluginByKey(string $key): ?BasePlugin + { + if (! str_contains($key, '/')) { + return null; + } + + $keyArray = explode('/', $key); + return $this->getPlugin($keyArray[0], $keyArray[1]); + } + + /** + * @param value-of $name + * @param array $arguments + */ + public function runHook(string $name, array $arguments): void + { + foreach (static::$plugins as $plugin) { + // only run hook on active plugins + if ($plugin->getStatus() !== PluginStatus::ACTIVE) { + continue; + } + + if (! $plugin->isHookDeclared($name)) { + continue; + } + + $plugin->{$name}(...$arguments); + } + } + + public function activate(BasePlugin $plugin): void + { + if ($plugin->activate()) { + ++self::$activeCount; + } + } + + public function deactivate(BasePlugin $plugin): void + { + if ($plugin->deactivate()) { + --self::$activeCount; + } + } + + /** + * @param ?array{'podcast'|'episode',int} $additionalContext + */ + public function setOption(BasePlugin $plugin, string $name, mixed $value, ?array $additionalContext = null): void + { + set_plugin_setting($plugin->getKey(), $name, $value, $additionalContext); + } + + public function getInstalledCount(): int + { + return static::$installedCount; + } + + public function getActiveCount(): int + { + return static::$activeCount; + } + + public function uninstall(BasePlugin $plugin): bool + { + // remove all settings data + $db = Database::connect(); + $builder = $db->table('settings'); + + $db->transStart(); + $builder->where('class', self::class); + $builder->like('context', sprintf('plugin:%s', $plugin->getKey() . '%')); + + if (! $builder->delete()) { + $db->transRollback(); + return false; + } + + return $db->transCommit(); + } + + protected function registerPlugins(): void + { + // search for plugins in plugins folder + $pluginsDirectories = glob($this->config->folder . '*/*', GLOB_ONLYDIR); + + if ($pluginsDirectories === false || $pluginsDirectories === []) { + return; + } + + foreach ($pluginsDirectories as $pluginDirectory) { + $vendor = basename(dirname($pluginDirectory)); + $package = basename($pluginDirectory); + + if (preg_match('~' . PLUGINS_KEY_PATTERN . '~', $vendor . '/' . $package) === false) { + continue; + } + + $className = str_replace( + ' ', + '', + ucwords(str_replace(['-', '_', '.'], ' ', $vendor . ' ' . $package)) . 'Plugin', + ); + + $pluginFile = $pluginDirectory . DIRECTORY_SEPARATOR . 'Plugin.php'; + spl_autoload_register(static function ($class) use (&$className, &$pluginFile): void { + if ($class !== $className) { + return; + } + + if (! file_exists($pluginFile)) { + return; + } + + include_once $pluginFile; + }, true); + + if (! class_exists($className)) { + continue; + } + + $plugin = new $className($vendor, $package, $pluginDirectory); + if (! $plugin instanceof BasePlugin) { + continue; + } + + static::$plugins[] = $plugin; + static::$pluginsByVendor[$vendor][] = $plugin; + ++static::$installedCount; + + if ($plugin->getStatus() === PluginStatus::ACTIVE) { + ++static::$activeCount; + } + } + } +} diff --git a/modules/Plugins/Core/RSS.php b/modules/Plugins/Core/RSS.php new file mode 100644 index 00000000..36a96fe7 --- /dev/null +++ b/modules/Plugins/Core/RSS.php @@ -0,0 +1,43 @@ +rss; + } + + /** + * @return ?RssFeed[] + */ + public function toSimpleRSS(): ?array + { + try { + $rssFeed = new RssFeed("{$this->rss}"); + } catch (Exception) { + return null; + } + + return [ + ...$rssFeed->children(), + ...$rssFeed->children(RssFeed::ATOM_NS, true), + ...$rssFeed->children(RssFeed::ITUNES_NS, true), + ...$rssFeed->children(RssFeed::PODCAST_NS, true), + ]; + } +} diff --git a/modules/Plugins/ExternalImageProcessor.php b/modules/Plugins/ExternalImageProcessor.php new file mode 100644 index 00000000..f5947aa3 --- /dev/null +++ b/modules/Plugins/ExternalImageProcessor.php @@ -0,0 +1,45 @@ +getDocument(); + $walker = $document->walker(); + while ($event = $walker->next()) { + $node = $event->getNode(); + + // Only stop at Link nodes when we first encounter them + if (! ($node instanceof Image) || ! $event->isEntering()) { + continue; + } + + $url = $node->getUrl(); + if ($this->isUrlExternal($url)) { + $node->detach(); + } + } + } + + private function isUrlExternal(string $url): bool + { + // Only look at http and https URLs + if (! preg_match('/^https?:\/\//', $url)) { + return false; + } + + $host = parse_url($url, PHP_URL_HOST); + + // TODO: load from environment's config + return $host !== new URI(base_url()) + ->getHost(); + } +} diff --git a/modules/Plugins/ExternalLinkProcessor.php b/modules/Plugins/ExternalLinkProcessor.php new file mode 100644 index 00000000..0297609c --- /dev/null +++ b/modules/Plugins/ExternalLinkProcessor.php @@ -0,0 +1,46 @@ +getDocument(); + $walker = $document->walker(); + while ($event = $walker->next()) { + $node = $event->getNode(); + + // Only stop at Link nodes when we first encounter them + if (! ($node instanceof Link) || ! $event->isEntering()) { + continue; + } + + $url = $node->getUrl(); + if ($this->isUrlExternal($url)) { + $node->data->append('attributes/target', '_blank'); + $node->data->append('attributes/rel', 'noopener noreferrer'); + } + } + } + + private function isUrlExternal(string $url): bool + { + // Only look at http and https URLs + if (! preg_match('/^https?:\/\//', $url)) { + return false; + } + + $host = parse_url($url, PHP_URL_HOST); + + // TODO: load from environment's config + return $host !== new URI(base_url()) + ->getHost(); + } +} diff --git a/modules/Plugins/Helpers/plugins_helper.php b/modules/Plugins/Helpers/plugins_helper.php new file mode 100644 index 00000000..3a2c926d --- /dev/null +++ b/modules/Plugins/Helpers/plugins_helper.php @@ -0,0 +1,83 @@ +get($key, $context); + } +} + +if (! function_exists('set_plugin_setting')) { + /** + * @param ?array{'podcast'|'episode',int} $additionalContext + */ + function set_plugin_setting( + string $pluginKey, + string $option, + mixed $value = null, + ?array $additionalContext = null, + ): void { + $key = sprintf('Plugins.%s', $option); + $context = sprintf('plugin:%s', $pluginKey); + + if ($additionalContext !== null) { + $context .= sprintf('+%s:%d', ...$additionalContext); + } + + setting() + ->set($key, $value, $context); + } +} + +if (! function_exists('load_plugins_translations')) { + /** + * @return array + */ + function load_plugins_translations(string $locale): array + { + $allPlugins = plugins() + ->getAllPlugins(); + + $translations = []; + foreach ($allPlugins as $plugin) { + $file = $plugin->getDirectory() . DIRECTORY_SEPARATOR . 'i18n' . DIRECTORY_SEPARATOR . $locale . '.json'; + + $jsonContents = @file_get_contents($file); + + if (! $jsonContents) { + continue; + } + + $contents = json_decode($jsonContents, true); + + if (! $contents) { + continue; + } + + $translations[$plugin->getKey()] = $contents; + } + + return $translations; + } +} diff --git a/modules/Plugins/Language/br/Plugin.php b/modules/Plugins/Language/br/Plugin.php new file mode 100644 index 00000000..36a992fa --- /dev/null +++ b/modules/Plugins/Language/br/Plugin.php @@ -0,0 +1,8 @@ + 'Plugins installed', + 'about' => 'About', + 'website' => 'Website', + 'repository' => 'Code repository', + 'authors' => 'Authors', + 'author_email' => 'Email {authorName}', + 'author_homepage' => '{authorName} homepage', + 'declaredHooks' => 'Declared hooks', + 'settings' => 'Settings', + 'settingsTitle' => '{type, select, + podcast {{pluginTitle} podcast settings} + episode {{pluginTitle} episode settings} + other {{pluginTitle} general settings} + }', + 'view' => 'View', + 'activate' => 'Activate', + 'deactivate' => 'Deactivate', + 'active' => 'Active', + 'inactive' => 'Inactive', + 'invalid' => 'Invalid', + 'incompatible' => 'Incompatible', + 'incompatible_hint' => 'Plugin requires Castopod v{minCastopodVersion} minimum.', + 'uninstall' => 'Uninstall', + 'keywords' => [ + 'podcasting20' => 'Podcasting 2.0', + 'seo' => 'SEO', + 'analytics' => 'Analytics', + 'accessibility' => 'Accessibility', + ], + 'noDescription' => 'No description', + 'noReadme' => 'No README file found.', + 'messages' => [ + 'saveSettingsSuccess' => '{pluginTitle} settings were successfully saved!', + ], + 'errors' => [ + 'manifestError' => 'Plugin manifest has errors', + 'manifestMissing' => 'Plugin manifest "{manifestPath}" is missing.', + 'manifestJsonInvalid' => 'Plugin manifest "{manifestPath}" is not a valid JSON.', + ], +]; diff --git a/modules/Plugins/Language/es/Plugin.php b/modules/Plugins/Language/es/Plugin.php new file mode 100644 index 00000000..36a992fa --- /dev/null +++ b/modules/Plugins/Language/es/Plugin.php @@ -0,0 +1,8 @@ + 'permit_empty|in_list[checkbox,datetime,email,group,html,markdown,number,radio-group,rss,select-multiple,select,text,textarea,toggler,url]', + 'key' => 'required|alpha_dash', + 'label' => 'required|string', + 'hint' => 'permit_empty|string', + 'helper' => 'permit_empty|string', + 'validationRules' => 'permit_empty|is_string_or_list', + 'optional' => 'permit_empty|is_boolean', + 'multiple' => 'permit_empty|is_boolean', + ]; + + protected string $type = 'text'; + + protected string $key; + + protected string $label; + + protected string $hint = ''; + + protected string $helper = ''; + + /** + * @var string[] + */ + protected array $validationRules = []; + + protected bool $optional = false; + + protected bool $multiple = false; + + public function getLabel(): string + { + return $this->getTranslated('label'); + } + + public function getHint(): string + { + return $this->getTranslated('hint'); + } + + public function getHelper(): string + { + return $this->getTranslated('helper'); + } + + /** + * @param string|list $values + */ + public function setValidationRules(string|array $values): void + { + $validationRules = []; + if (is_string($values)) { + $validationRules = explode('|', $values); + } + + $allowedRules = [ + 'alpha', + 'alpha_dash', + 'alpha_numeric', + 'alpha_numeric_punct', + 'alpha_numeric_space', + 'alpha_space', + 'decimal', + 'differs', + 'exact_length', + 'greater_than', + 'greater_than_equal_to', + 'hex', + 'in_list', + 'integer', + 'is_natural', + 'is_natural_no_zero', + 'less_than', + 'less_than_equal_to', + 'max_length', + 'min_length', + 'not_in_list', + 'regex_match', + 'valid_base64', + 'valid_date', + ]; + foreach ($validationRules as $rule) { + foreach ($allowedRules as $allowedRule) { + if (str_starts_with($rule, $allowedRule)) { + $this->validationRules[] = $rule; + } + } + } + } + + public function render(string $name, mixed $value, string $class = ''): string + { + throw new RuntimeException('Render function not defined in parent Field class'); + } + + private function getTranslated(string $property): string + { + $key = sprintf('Plugin.%s.settings.%s.%s.%s', $this->pluginKey, $this->type, $this->key, $property); + + /** @var string $i18nField */ + $i18nField = lang($key); + + if ($this->{$property} === '' || $i18nField === $key) { + return esc($this->{$property}); + } + + return esc($i18nField); + } +} diff --git a/modules/Plugins/Manifest/FieldInterface.php b/modules/Plugins/Manifest/FieldInterface.php new file mode 100644 index 00000000..2fcbaa23 --- /dev/null +++ b/modules/Plugins/Manifest/FieldInterface.php @@ -0,0 +1,10 @@ + 'permit_empty|is_boolean', + ]; + + protected bool $defaultValue = false; + + public function render(string $name, mixed $value, string $class = ''): string + { + $value = $value ? 'yes' : ''; + return <<{$this->label} + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Datetime.php b/modules/Plugins/Manifest/Fields/Datetime.php new file mode 100644 index 00000000..d7a5826f --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Datetime.php @@ -0,0 +1,37 @@ + 'permit_empty|valid_date', + ]; + + protected string $defaultValue = ''; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Email.php b/modules/Plugins/Manifest/Fields/Email.php new file mode 100644 index 00000000..50f215f0 --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Email.php @@ -0,0 +1,38 @@ + 'permit_empty|valid_email', + ]; + + protected string $defaultValue = ''; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Group.php b/modules/Plugins/Manifest/Fields/Group.php new file mode 100644 index 00000000..2cb89f7d --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Group.php @@ -0,0 +1,36 @@ +injectRules(); + + parent::__construct($pluginKey); + } + + #[Override] + public function loadData(array $data): void + { + $data = $this->transformData($data); + + parent::loadData($data); + } + + public function render(string $name, mixed $value, string $class = ''): string + { + // TODO: render group, depending on multiple + throw new RuntimeException('Render function not defined in Group Field class'); + } +} diff --git a/modules/Plugins/Manifest/Fields/Html.php b/modules/Plugins/Manifest/Fields/Html.php new file mode 100644 index 00000000..f5f112cd --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Html.php @@ -0,0 +1,39 @@ + 'permit_empty|string', + ]; + + protected string $defaultValue = ''; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + $value = htmlspecialchars($value ?? ''); + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Markdown.php b/modules/Plugins/Manifest/Fields/Markdown.php new file mode 100644 index 00000000..4105a628 --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Markdown.php @@ -0,0 +1,37 @@ + 'permit_empty|string', + ]; + + protected string $defaultValue = ''; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Number.php b/modules/Plugins/Manifest/Fields/Number.php new file mode 100644 index 00000000..949eea5a --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Number.php @@ -0,0 +1,38 @@ + 'permit_empty|numeric', + ]; + + protected ?int $defaultValue = null; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/RadioGroup.php b/modules/Plugins/Manifest/Fields/RadioGroup.php new file mode 100644 index 00000000..d7b38229 --- /dev/null +++ b/modules/Plugins/Manifest/Fields/RadioGroup.php @@ -0,0 +1,57 @@ + 'permit_empty|string', + ]; + + protected string $defaultValue = ''; + + public function __construct(string $pluginKey) + { + $this->injectRules(); + + parent::__construct($pluginKey); + } + + #[Override] + public function loadData(array $data): void + { + $data = $this->transformData($data); + + parent::loadData($data); + } + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + $options = esc(json_encode($this->getOptionsArray())); + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Rss.php b/modules/Plugins/Manifest/Fields/Rss.php new file mode 100644 index 00000000..3e085317 --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Rss.php @@ -0,0 +1,40 @@ + 'permit_empty|string', + ]; + + protected string $defaultValue = ''; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + $value = htmlspecialchars((string) $value); + $defaultValue = esc($this->defaultValue); + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Select.php b/modules/Plugins/Manifest/Fields/Select.php new file mode 100644 index 00000000..7fa8f9d2 --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Select.php @@ -0,0 +1,64 @@ + 'permit_empty|string', + 'options' => 'is_list', + ]; + + protected array $casts = [ + 'options' => [Option::class], + ]; + + protected string $defaultValue = ''; + + public function __construct(string $pluginKey) + { + $this->injectRules(); + + parent::__construct($pluginKey); + } + + #[Override] + public function loadData(array $data): void + { + $data = $this->transformData($data); + + parent::loadData($data); + } + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + $options = esc(json_encode($this->getOptionsArray())); + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/SelectMultiple.php b/modules/Plugins/Manifest/Fields/SelectMultiple.php new file mode 100644 index 00000000..7a2e6e7f --- /dev/null +++ b/modules/Plugins/Manifest/Fields/SelectMultiple.php @@ -0,0 +1,63 @@ + $defaultValue + */ +class SelectMultiple extends Field +{ + use WithOptionsTrait; + + public static array $validation_rules = [ + 'defaultValue' => 'permit_empty|is_list', + ]; + + /** + * @var list + */ + protected array $defaultValue = []; + + public function __construct(string $pluginKey) + { + $this->injectRules(); + + parent::__construct($pluginKey); + } + + #[Override] + public function loadData(array $data): void + { + $data = $this->transformData($data); + + parent::loadData($data); + } + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + $options = esc(json_encode($this->getOptionsArray())); + $value = esc(json_encode($value)); + $defaultValue = esc(json_encode($this->defaultValue)); + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Text.php b/modules/Plugins/Manifest/Fields/Text.php new file mode 100644 index 00000000..67ee93aa --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Text.php @@ -0,0 +1,37 @@ + 'permit_empty|string', + ]; + + protected string $defaultValue = ''; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Textarea.php b/modules/Plugins/Manifest/Fields/Textarea.php new file mode 100644 index 00000000..afb025a5 --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Textarea.php @@ -0,0 +1,37 @@ + 'permit_empty|string', + ]; + + protected string $defaultValue = ''; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Toggler.php b/modules/Plugins/Manifest/Fields/Toggler.php new file mode 100644 index 00000000..655dc8df --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Toggler.php @@ -0,0 +1,34 @@ + 'permit_empty|is_boolean', + ]; + + protected bool $defaultValue = false; + + public function render(string $name, mixed $value, string $class = ''): string + { + $value = $value ? 'yes' : ''; + return <<{$this->label} + HTML; + } +} diff --git a/modules/Plugins/Manifest/Fields/Url.php b/modules/Plugins/Manifest/Fields/Url.php new file mode 100644 index 00000000..3d712d62 --- /dev/null +++ b/modules/Plugins/Manifest/Fields/Url.php @@ -0,0 +1,39 @@ + 'permit_empty|valid_url_strict', + ]; + + protected string $defaultValue = ''; + + public function render(string $name, mixed $value, string $class = ''): string + { + $isRequired = $this->optional ? 'false' : 'true'; + return << + HTML; + } +} diff --git a/modules/Plugins/Manifest/Manifest.php b/modules/Plugins/Manifest/Manifest.php new file mode 100644 index 00000000..6c098d6c --- /dev/null +++ b/modules/Plugins/Manifest/Manifest.php @@ -0,0 +1,84 @@ + $keywords + * @property ?string $minCastopodVersion + * @property list $hooks + * @property ?Settings $settings + * @property ?Repository $repository + */ +class Manifest extends ManifestObject +{ + public static array $validation_rules = [ + 'name' => 'required|max_length[128]|regex_match[/^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9]([_.-]?[a-z0-9]+)*$/]', + 'version' => 'required|regex_match[/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/]', + 'description' => 'permit_empty|max_length[256]', + 'authors' => 'permit_empty|is_list', + 'homepage' => 'permit_empty|valid_url_strict', + 'license' => 'permit_empty|string', + 'private' => 'permit_empty|is_boolean', + 'submodule' => 'permit_empty|is_boolean', + 'keywords.*' => 'permit_empty', + 'minCastopodVersion' => 'permit_empty|regex_match[/^(0|[1-9]\d*)\.(0|[1-9]\d*)(\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/]', + 'hooks.*' => 'permit_empty|in_list[rssBeforeChannel,rssAfterChannel,rssBeforeItem,rssAfterItem,siteHead]', + 'settings' => 'permit_empty|is_list', + 'repository' => 'permit_empty|is_list', + ]; + + protected array $casts = [ + 'authors' => [Person::class], + 'homepage' => URI::class, + 'settings' => Settings::class, + 'repository' => Repository::class, + ]; + + protected ?string $name = '???'; + + protected ?string $version = 'X.Y.Z'; + + protected ?string $description = null; + + /** + * @var Person[] + */ + protected array $authors = []; + + protected ?URI $homepage = null; + + protected ?string $license = null; + + protected bool $private = false; + + protected bool $submodule = false; + + /** + * @var list + */ + protected array $keywords = []; + + protected ?string $minCastopodVersion = null; + + /** + * @var list + */ + protected array $hooks = []; + + protected ?Settings $settings = null; + + protected ?Repository $repository = null; +} diff --git a/modules/Plugins/Manifest/ManifestObject.php b/modules/Plugins/Manifest/ManifestObject.php new file mode 100644 index 00000000..4070d24f --- /dev/null +++ b/modules/Plugins/Manifest/ManifestObject.php @@ -0,0 +1,166 @@ + + */ + public static array $validation_rules = []; + + /** + * @var array + */ + protected array $casts = []; + + /** + * @var array> + */ + protected static array $errors = []; + + public function __construct( + protected readonly string $pluginKey, + ) { + self::$errors[$pluginKey] = []; + + $class = static::class; + $validation_rules = []; + $casts = []; + while ($class = get_parent_class($class)) { + $validation_rules = [...$validation_rules, ...get_class_vars($class)['validation_rules']]; + $casts = [...$casts, ...get_class_vars($class)['casts']]; + } + + $this::$validation_rules = [...$validation_rules, ...$this::$validation_rules]; + $this->casts = [...$casts, ...$this->casts]; + } + + public function __get(string $name): mixed + { + if (property_exists($this, $name)) { + // if a get method exists for this property, return that + $method = 'get' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $name))); + if (method_exists($this, $method)) { + return $this->{$method}(); + } + + return $this->{$name}; + } + + throw new Exception('Undefined object property ' . static::class . '::' . $name); + } + + public function __isset(string $property): bool + { + return property_exists($this, $property); + } + + public function loadFromFile(string $manifestPath): void + { + $manifestContents = @file_get_contents($manifestPath); + + if (! $manifestContents) { + $manifestContents = '{}'; + $this->addError('manifest', lang('Plugins.errors.manifestMissing', [ + 'manifestPath' => $manifestPath, + ])); + } + + /** @var array|null $manifestData */ + $manifestData = json_decode($manifestContents, true); + + if ($manifestData === null) { + $manifestData = []; + $this->addError('manifest', lang('Plugins.errors.manifestJsonInvalid', [ + 'manifestPath' => $manifestPath, + ])); + } + + $this->loadData($manifestData); + } + + /** + * @param array $data + */ + public function loadData(array $data): void + { + /** @var Validation $validation */ + $validation = service('validation'); + + $validation->setRules($this::$validation_rules); + + if (! $validation->run($data)) { + foreach ($validation->getErrors() as $key => $message) { + $this->addError($key, $message); + } + + $validation->reset(); + } + + foreach ($validation->getValidated() as $key => $value) { + $method = 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))); + if (is_callable([$this, $method])) { + $this->{$method}($value); + continue; + } + + if (array_key_exists($key, $this->casts)) { + $cast = $this->casts[$key]; + if (is_array($cast) && is_array($value)) { + foreach ($value as $valueKey => $valueElement) { + if (is_subclass_of($cast[0], self::class)) { + $manifestClass = $cast[0] === Field::class ? $this->getFieldClass( + $valueElement, + ) : $cast[0]; + $value[$valueKey] = new $manifestClass($this->pluginKey); + $value[$valueKey]->loadData($valueElement); + } else { + $value[$valueKey] = new $cast[0]($valueElement); + } + } + } elseif (is_subclass_of($cast, self::class)) { + $manifestClass = $cast === Field::class ? $this->getFieldClass($value) : $cast; + $valueElement = $value; + $value = new $manifestClass($this->pluginKey); + $value->loadData($valueElement ?? []); + } else { + $value = new $cast($value ?? []); + } + } + + $this->{$key} = $value; + } + } + + /** + * @return array + */ + public static function getPluginErrors(string $pluginKey): array + { + return self::$errors[$pluginKey]; + } + + protected function addError(string $errorKey, string $errorMessage): void + { + self::$errors[$this->pluginKey][$errorKey] = $errorMessage; + } + + /** + * @param array $data + */ + private function getFieldClass(array $data): string + { + $fieldType = $data['type'] ?? 'text'; + return rtrim(Field::class, "\Field") . '\\Fields\\' . str_replace( + ' ', + '', + ucwords(str_replace('-', ' ', $fieldType)), + ); + } +} diff --git a/modules/Plugins/Manifest/Option.php b/modules/Plugins/Manifest/Option.php new file mode 100644 index 00000000..ff15180b --- /dev/null +++ b/modules/Plugins/Manifest/Option.php @@ -0,0 +1,39 @@ + 'required|string', + 'value' => 'required|alpha_numeric_punct', + 'description' => 'permit_empty|string', + ]; + + protected string $label; + + protected string $value; + + protected string $description = ''; + + public function getTranslated(string $i18nKey, string $property): string + { + $key = sprintf('%s.%s.%s', $i18nKey, $this->value, $property); + + /** @var string $i18nField */ + $i18nField = lang($key); + + if ($this->{$property} === '' || $i18nField === $key) { + return esc($this->{$property}); + } + + return esc($i18nField); + } +} diff --git a/modules/Plugins/Manifest/Person.php b/modules/Plugins/Manifest/Person.php new file mode 100644 index 00000000..1a11c931 --- /dev/null +++ b/modules/Plugins/Manifest/Person.php @@ -0,0 +1,58 @@ +[^<>()]*)\s*(<(?.*)>)?\s*(\((?.*)\))?$/'; + + public static array $validation_rules = [ + 'name' => 'required', + 'email' => 'permit_empty|valid_email', + 'url' => 'permit_empty|valid_url_strict', + ]; + + /** + * @var array + */ + protected array $casts = [ + 'url' => URI::class, + ]; + + protected string $name; + + protected ?string $email = null; + + protected ?URI $url = null; + + #[Override] + public function loadData(array|string $data): void + { + if (is_string($data)) { + $result = preg_match(self::AUTHOR_STRING_PATTERN, $data, $matches); + + if (! $result) { + throw new Exception('Author string is not valid.'); + } + + $data = [ + 'name' => $matches['name'], + 'email' => $matches['email'] ?? null, + 'url' => $matches['url'] ?? null, + ]; + } + + parent::loadData($data); + } +} diff --git a/modules/Plugins/Manifest/Repository.php b/modules/Plugins/Manifest/Repository.php new file mode 100644 index 00000000..9a50398e --- /dev/null +++ b/modules/Plugins/Manifest/Repository.php @@ -0,0 +1,34 @@ + 'permit_empty|in_list[git]', + 'url' => 'required|valid_url_strict', + 'directory' => 'permit_empty', + ]; + + /** + * @var array + */ + protected array $casts = [ + 'url' => URI::class, + ]; + + protected string $type = 'git'; + + protected URI $url; + + protected ?string $directory = null; +} diff --git a/modules/Plugins/Manifest/Settings.php b/modules/Plugins/Manifest/Settings.php new file mode 100644 index 00000000..a30f3635 --- /dev/null +++ b/modules/Plugins/Manifest/Settings.php @@ -0,0 +1,62 @@ + 'permit_empty|is_list', + 'podcast' => 'permit_empty|is_list', + 'episode' => 'permit_empty|is_list', + ]; + + /** + * @var array + */ + protected array $casts = [ + 'general' => [Field::class], + 'podcast' => [Field::class], + 'episode' => [Field::class], + ]; + + /** + * @var Field[] + */ + protected array $general = []; + + /** + * @var Field[] + */ + protected array $podcast = []; + + /** + * @var Field[] + */ + protected array $episode = []; + + #[Override] + public function loadData(array $data): void + { + $newData = []; + foreach ($data as $key => $fields) { + $newFields = []; + foreach ($fields as $fieldKey => $field) { + $field['key'] = $fieldKey; + $newFields[] = $field; + } + + $newData[$key] = $newFields; + } + + parent::loadData($newData); + } +} diff --git a/modules/Plugins/Manifest/WithFieldsTrait.php b/modules/Plugins/Manifest/WithFieldsTrait.php new file mode 100644 index 00000000..72ac75a2 --- /dev/null +++ b/modules/Plugins/Manifest/WithFieldsTrait.php @@ -0,0 +1,45 @@ + 'is_list', + ]]; + $this->casts = [...$this->casts, ...[ + 'fields' => [Field::class], + ]]; + } + + /** + * @param array $data + * @return array + */ + public function transformData(array $data): array + { + if (array_key_exists('fields', $data)) { + $newFields = []; + foreach ($data['fields'] as $key => $field) { + $field['key'] = $key; + $newFields[] = $field; + } + + $data['fields'] = $newFields; + } + + return $data; + } +} diff --git a/modules/Plugins/Manifest/WithOptionsTrait.php b/modules/Plugins/Manifest/WithOptionsTrait.php new file mode 100644 index 00000000..a88b0ad3 --- /dev/null +++ b/modules/Plugins/Manifest/WithOptionsTrait.php @@ -0,0 +1,69 @@ + 'is_list', + ]]; + } + + if (isset($this->casts)) { + $this->casts = [...$this->casts, ...[ + 'options' => [Option::class], + ]]; + } + } + + /** + * @param array $data + * @return array + */ + public function transformData(array $data): array + { + if (array_key_exists('options', $data)) { + $newOptions = []; + foreach ($data['options'] as $key => $option) { + $option['value'] = $key; + $newOptions[] = $option; + } + + $data['options'] = $newOptions; + } + + return $data; + } + + /** + * @return array{label:string,value:string,description:string}[] + */ + public function getOptionsArray(): array + { + $i18nKey = sprintf('%s.settings.%s.%s.options', $this->pluginKey, $this->type, $this->key); + + $optionsArray = []; + foreach ($this->options as $option) { + $optionsArray[] = [ + 'value' => $option->value, + 'label' => $option->getTranslated($i18nKey, 'label'), + 'description' => $option->getTranslated($i18nKey, 'description'), + ]; + } + + return $optionsArray; + } +} diff --git a/modules/Plugins/Manifest/manifest.schema.json b/modules/Plugins/Manifest/manifest.schema.json new file mode 100644 index 00000000..08ecd0eb --- /dev/null +++ b/modules/Plugins/Manifest/manifest.schema.json @@ -0,0 +1,409 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "/schemas/manifest.json", + "title": "JSON schema for Castopod Plugins's manifest.json files", + "description": "The Castopod plugin manifest defines both metadata and behavior of a plugin", + "type": "object", + "properties": { + "name": { + "description": "The plugin name, including 'vendor-name/' prefix", + "type": "string", + "pattern": "^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9]([_.-]?[a-z0-9]+)*$", + "examples": ["acme/hello-world"] + }, + "version": { + "description": "The plugin's semantic version. See https://semver.org/", + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", + "examples": ["1.0.0"] + }, + "minCastopodVersion": { + "description": "The minimal version of Castopod for which the plugin is compatible with.", + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(\\.(0|[1-9]\\d*))?(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", + "examples": ["2.0"] + }, + "description": { + "description": "This helps people discover your plugin as it's listed in repositories", + "type": "string" + }, + "authors": { + "type": "array", + "items": { + "$ref": "#/$defs/person" + } + }, + "homepage": { + "description": "The URL to the plugin homepage", + "type": "string", + "format": "uri" + }, + "license": { + "description": "You should specify a license for your plugin so that people know how they are permitted to use it, and any restrictions you're placing on it.", + "default": "UNLICENSED", + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "AGPL-3.0-only", + "AGPL-3.0-or-later", + "Apache-2.0", + "BSL-1.0", + "GPL-3.0-only", + "GPL-3.0-or-later", + "LGPL-3.0-only", + "LGPL-3.0-or-later", + "MIT", + "MPL-2.0", + "Unlicense", + "UNLICENSED" + ] + } + ] + }, + "private": { + "type": "boolean", + "description": "If set to true, then repositories should refuse to publish it.", + "default": false + }, + "submodule": { + "type": "boolean", + "description": "Set to `true` if the plugin is part of a monorepo (i.e., in the same Git repository as other plugins). Releases should be tagged as `@` to ensure proper version indexing by plugin repositories.", + "default": false + }, + "keywords": { + "description": "This helps people discover your plugin as it's listed in repositories", + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "accessibility", + "analytics", + "monetization", + "podcasting2", + "privacy", + "productivity", + "seo" + ] + } + ] + }, + "uniqueItems": true + }, + "hooks": { + "description": "The hooks used by the plugin.", + "type": "array", + "items": { + "enum": [ + "rssBeforeChannel", + "rssAfterChannel", + "rssBeforeItem", + "rssAfterItem", + "siteHead" + ] + }, + "uniqueItems": true + }, + "settings": { + "type": "object", + "properties": { + "general": { + "$ref": "#/$defs/fields" + }, + "podcast": { + "$ref": "#/$defs/fields" + }, + "episode": { + "$ref": "#/$defs/fields" + } + } + }, + "files": { + "description": "List of files to include in your plugin package. If you include a folder in the array, all files inside it will also be included.", + "type": "array", + "items": { + "type": "string" + } + }, + "repository": { + "description": "Specify the place where your plugin code lives. This is helpful for people who want to contribute.", + "type": ["object", "string"], + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "directory": { + "type": "string" + } + } + } + }, + "required": ["name", "version", "minCastopodVersion"], + "additionalProperties": false, + "$defs": { + "person": { + "description": "A person who has been involved in creating or maintaining this plugin.", + "type": ["object", "string"], + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "url": { + "type": "string", + "format": "uri" + } + } + }, + "fields": { + "type": "object", + "patternProperties": { + "^[A-Za-z]+[\\w\\-\\:\\.]*$": { "$ref": "#/$defs/field" } + }, + "additionalProperties": false + }, + "field": { + "type": "object", + "properties": { + "type": { + "enum": [ + "checkbox", + "datetime", + "email", + "group", + "html", + "markdown", + "number", + "radio-group", + "rss", + "select-multiple", + "select", + "text", + "textarea", + "toggler", + "url" + ], + "default": "text" + }, + "label": { + "type": "string" + }, + "hint": { + "type": "string" + }, + "helper": { + "type": "string" + }, + "optional": { + "type": "boolean" + }, + "validationRules": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "multiple": { + "type": "boolean" + } + }, + "required": ["label"], + "unevaluatedProperties": false, + "allOf": [ + { "$ref": "#/$defs/field-multiple-implies-options-is-required" }, + { "$ref": "#/$defs/require-fields-for-group-type" }, + { "$ref": "#/$defs/default-value-based-on-type" } + ] + }, + "option": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": ["label"], + "additionalProperties": false + }, + "field-multiple-implies-options-is-required": { + "if": { + "properties": { + "type": { + "enum": ["radio-group", "select", "select-multiple"] + } + } + }, + "then": { + "properties": { + "options": { + "type": "object", + "patternProperties": { + "^[A-Za-z0-9]+[\\w\\-\\:\\.]*$": { "$ref": "#/$defs/option" } + }, + "additionalProperties": false + } + }, + "required": ["options"] + } + }, + "require-fields-for-group-type": { + "if": { + "properties": { + "type": { + "const": "group" + } + } + }, + "then": { + "properties": { + "fields": { + "type": "object", + "patternProperties": { + "^[A-Za-z]+[\\w\\-\\:\\.]*$": { "$ref": "#/$defs/field" } + }, + "additionalProperties": false + } + }, + "required": ["fields"] + } + }, + "default-value-based-on-type": { + "allOf": [ + { + "if": { + "properties": { + "type": { + "enum": [ + "html", + "markdown", + "radio-group", + "rss", + "select", + "text", + "textarea" + ] + } + } + }, + "then": { + "properties": { + "defaultValue": { "type": "string" } + } + } + }, + { + "if": { + "properties": { + "type": { + "enum": ["checkbox", "toggler"] + } + } + }, + "then": { + "properties": { + "defaultValue": { "type": "boolean" } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "datetime" + } + } + }, + "then": { + "properties": { + "defaultValue": { "type": "string", "format": "date-time" } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "email" + } + } + }, + "then": { + "properties": { + "defaultValue": { "type": "string", "format": "email" } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "number" + } + } + }, + "then": { + "properties": { + "defaultValue": { "type": "number" } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "select-multiple" + } + } + }, + "then": { + "properties": { + "defaultValue": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "url" + } + } + }, + "then": { + "properties": { + "defaultValue": { "type": "string", "format": "uri" } + } + } + } + ] + } + } +} diff --git a/modules/PodcastImport/Commands/PodcastImport.php b/modules/PodcastImport/Commands/PodcastImport.php new file mode 100644 index 00000000..63c835e1 --- /dev/null +++ b/modules/PodcastImport/Commands/PodcastImport.php @@ -0,0 +1,599 @@ + $task->status === TaskStatus::Running, + ), + ); + + if ($currentImport instanceof PodcastImportTask) { + $currentImport->syncWithProcess(); + + if ($currentImport->status === TaskStatus::Running) { + // process is still running + throw new Exception('An import is already running.'); + } + + // continue if the task is not running anymore + } + + // Get the next queued import + $queuedImports = array_filter( + $importQueue, + static fn (PodcastImportTask $task): bool => $task->status === TaskStatus::Queued, + ); + $nextImport = end($queuedImports); + + if (! $nextImport instanceof PodcastImportTask) { + // no queued import task, stop process. + exit(0); + } + + $this->importTask = $nextImport; + + // retrieve user who created import task + $user = new UserModel() + ->find($this->importTask->created_by); + + if (! $user instanceof User) { + throw new Exception('Could not retrieve user with ID: ' . $this->importTask->created_by); + } + + $this->user = $user; + + CLI::write('Fetching and parsing RSS feed...'); + + ini_set('user_agent', 'Castopod/' . CP_VERSION); + $this->podcastFeed = new PodcastFeed($this->importTask->feed_url); + } + + #[Override] + public function run(array $params): void + { + // FIXME: getting named routes doesn't work from v4.3 anymore, so loading all routes before importing + service('routes') + ->loadRoutes(); + + try { + $this->init(); + + CLI::write('All good! Feed was parsed successfully!'); + + CLI::write( + 'Starting import for @' . $this->importTask->handle . ' using feed: ' . $this->importTask->feed_url, + ); + + // --- START IMPORT TASK --- + $this->importTask->start(); + + CLI::write('Checking if podcast is locked.'); + + if ($this->podcastFeed->channel->podcast_locked->getValue()) { + throw new Exception('🔒 Podcast is locked.'); + } + + CLI::write('Podcast is not locked, import can resume.'); + + // check if podcast to be imported already exists by guid if exists or handle otherwise + $podcastGuid = $this->podcastFeed->channel->podcast_guid->getValue(); + if ($podcastGuid !== null) { + $podcast = new PodcastModel() + ->where('guid', $podcastGuid) + ->first(); + } else { + $podcast = new PodcastModel() + ->where('handle', $this->importTask->handle) + ->first(); + } + + if ($podcast instanceof Podcast) { + if ($podcast->handle !== $this->importTask->handle) { + throw new Exception('Podcast was already imported with a different handle.'); + } + + CLI::write('Podcast handle already exists, using existing one.'); + $this->podcast = $podcast; + } + + helper(['media', 'misc', 'auth']); + + if (! $this->podcast instanceof Podcast) { + $this->podcast = $this->importPodcast(); + } + + CLI::write('Adding podcast platforms...'); + + $this->importPodcastPlatforms(); + + CLI::write('Adding persons - ' . count($this->podcastFeed->channel->podcast_persons) . ' elements.'); + + $this->importPodcastPersons(); + + $this->importEpisodes(); + + // set podcast publication date to the first ever published episode + $this->podcast->published_at = $this->getOldestEpisodePublicationDate( + $this->podcast->id, + ) ?? $this->podcast->created_at; + + $podcastModel = new PodcastModel(); + if (! $podcastModel->update($this->podcast->id, $this->podcast)) { + throw new Exception(print_r($podcastModel->errors(), true)); + } + + CLI::showProgress(false); + + // // done, set status to passed + $this->importTask->pass(); + } catch (Exception $exception) { + $this->error($exception->getMessage()); + log_message( + 'critical', + 'Error when importing ' . $this->importTask?->feed_url . PHP_EOL . $exception->getMessage() . PHP_EOL . $exception->getTraceAsString(), + ); + } + } + + private function getOldestEpisodePublicationDate(int $podcastId): ?Time + { + $result = new EpisodeModel() + ->builder() + ->selectMax('published_at', 'oldest_published_at') + ->where('podcast_id', $podcastId) + ->get() + ->getResultArray(); + + if ($result[0]['oldest_published_at'] === null) { + return null; + } + + return Time::createFromFormat('Y-m-d H:i:s', $result[0]['oldest_published_at']); + } + + private function importPodcast(): Podcast + { + $location = null; + if ($this->podcastFeed->channel->podcast_location->getValue() !== null) { + $location = new Location( + $this->podcastFeed->channel->podcast_location->getValue(), + $this->podcastFeed->channel->podcast_location->getAttribute('geo'), + $this->podcastFeed->channel->podcast_location->getAttribute('osm'), + ); + } + + if (($showNotes = $this->getShowNotes($this->podcastFeed->channel)) === null) { + throw new Exception('Missing channel show notes. Please include a tag.'); + } + + if (($coverUrl = $this->getCoverUrl($this->podcastFeed->channel)) === null) { + throw new Exception('Missing podcast cover. Please include an tag.'); + } + + if (($ownerName = $this->podcastFeed->channel->itunes_owner->itunes_name->getValue()) === null) { + throw new Exception( + 'Missing podcast owner name. Please include an tag inside the tag.', + ); + } + + if (($ownerEmail = $this->podcastFeed->channel->itunes_owner->itunes_email->getValue()) === null) { + throw new Exception( + 'Missing podcast owner email. Please include an tag inside the tag.', + ); + } + + $parentalAdvisory = null; + if ($this->podcastFeed->channel->itunes_explicit->getValue() !== null) { + $parentalAdvisory = $this->podcastFeed->channel->itunes_explicit->getValue() ? 'explicit' : 'clean'; + } + + $db = db_connect(); + $db->transStart(); + + $htmlConverter = new HtmlConverter(); + $podcast = new Podcast([ + 'created_by' => $this->user->id, + 'updated_by' => $this->user->id, + 'guid' => $this->podcastFeed->channel->podcast_guid->getValue(), + 'handle' => $this->importTask->handle, + 'imported_feed_url' => $this->importTask->feed_url, + 'new_feed_url' => url_to('podcast-rss-feed', $this->importTask->handle), + 'title' => $this->podcastFeed->channel->title->getValue(), + 'description_markdown' => $htmlConverter->convert($showNotes), + 'description_html' => $showNotes, + 'cover' => download_file($coverUrl), + 'banner' => null, + 'language_code' => $this->importTask->language, + 'category_id' => $this->importTask->category, + 'parental_advisory' => $parentalAdvisory, + 'owner_name' => $ownerName, + 'owner_email' => $ownerEmail, + 'publisher' => $this->podcastFeed->channel->itunes_author->getValue(), + 'type' => $this->podcastFeed->channel->itunes_type->getValue(), + 'copyright' => $this->podcastFeed->channel->copyright->getValue(), + 'is_blocked' => $this->podcastFeed->channel->itunes_block->getValue(), + 'is_completed' => $this->podcastFeed->channel->itunes_complete->getValue(), + 'location' => $location, + ]); + + $podcastModel = new PodcastModel(); + if (! ($podcastId = $podcastModel->insert($podcast, true))) { + $db->transRollback(); + throw new Exception(print_r($podcastModel->errors(), true)); + } + + $podcast->id = $podcastId; + + // set current user as podcast admin + // 1. create new group + config('AuthGroups') + ->generatePodcastAuthorizations($podcast->id); + add_podcast_group($this->user, $podcast->id, 'admin'); + + $db->transComplete(); // save podcast to database + + CLI::write('Podcast was successfully created!'); + + return $podcast; + } + + private function getShowNotes(Channel|Item $channelOrItem): ?string + { + if (! $channelOrItem instanceof Item) { + return $channelOrItem->description->getValue() ?? $channelOrItem->itunes_summary->getValue(); + } + + if ($channelOrItem->content_encoded->getValue() !== null) { + return $channelOrItem->content_encoded->getValue(); + } + + return $channelOrItem->description->getValue() ?? $channelOrItem->itunes_summary->getValue(); + } + + private function getCoverUrl(Channel|Item $channelOrItem): ?string + { + if ($channelOrItem->itunes_image->getAttribute('href') !== null) { + return $channelOrItem->itunes_image->getAttribute('href'); + } + + if ($channelOrItem instanceof Channel && $channelOrItem->image->url->getValue() !== null) { + return $channelOrItem->image->url->getValue(); + } + + return null; + } + + private function importPodcastPersons(): void + { + $personsCount = count($this->podcastFeed->channel->podcast_persons); + $currPersonsStep = 1; // for progress + foreach ($this->podcastFeed->channel->podcast_persons as $person) { + CLI::showProgress($currPersonsStep++, $personsCount); + $fullName = $person->getValue(); + $newPersonId = null; + $personModel = new PersonModel(); + if (($newPerson = $personModel->getPerson($fullName)) instanceof Person) { + $newPersonId = $newPerson->id; + } else { + $newPodcastPerson = new Person([ + 'created_by' => $this->user->id, + 'updated_by' => $this->user->id, + 'full_name' => $fullName, + 'unique_name' => slugify($fullName), + 'information_url' => $person->getAttribute('href'), + 'avatar' => download_file((string) $person->getAttribute('img')), + ]); + + if (! $newPersonId = $personModel->insert($newPodcastPerson)) { + throw new Exception(print_r($personModel->errors(), true)); + } + } + + $personGroup = $person->getAttribute('group'); + $personRole = $person->getAttribute('role'); + + // set default group and role if taxonomy is not found + $personGroupSlug = 'cast'; + $personRoleSlug = 'host'; + + if (array_key_exists(strtolower((string) $personGroup), ReversedTaxonomy::$taxonomy)) { + $personGroup = ReversedTaxonomy::$taxonomy[strtolower((string) $personGroup)]; + $personGroupSlug = $personGroup['slug']; + + if (array_key_exists(strtolower((string) $personRole), $personGroup['roles'])) { + $personRoleSlug = $personGroup['roles'][strtolower((string) $personRole)]['slug']; + } + } + + $podcastPersonModel = new PersonModel(); + if (! $podcastPersonModel->addPodcastPerson( + $this->podcast->id, + $newPersonId, + $personGroupSlug, + $personRoleSlug, + )) { + throw new Exception(print_r($podcastPersonModel->errors(), true)); + } + } + + CLI::showProgress(false); + } + + private function importPodcastPlatforms(): void + { + $platformTypes = [ + [ + 'name' => 'podcasting', + 'elements' => $this->podcastFeed->channel->podcast_ids, + 'count' => count($this->podcastFeed->channel->podcast_ids), + 'account_url_key' => 'url', + 'account_id_key' => 'id', + ], + [ + 'name' => 'social', + 'elements' => $this->podcastFeed->channel->podcast_socials, + 'count' => count($this->podcastFeed->channel->podcast_socials), + 'account_url_key' => 'accountUrl', + 'account_id_key' => 'accountId', + ], + [ + 'name' => 'funding', + 'elements' => $this->podcastFeed->channel->podcast_fundings, + 'count' => count($this->podcastFeed->channel->podcast_fundings), + 'account_url_key' => 'url', + 'account_id_key' => 'id', + ], + ]; + + $platforms = service('platforms'); + $platformModel = new PlatformModel(); + foreach ($platformTypes as $platformType) { + $platformsData = []; + $currPlatformStep = 1; // for progress + CLI::write($platformType['name'] . ' - ' . $platformType['count'] . ' elements'); + foreach ($platformType['elements'] as $platform) { + CLI::showProgress($currPlatformStep++, $platformType['count']); + $platformSlug = $platform->getAttribute('platform'); + $platformData = $platforms->findPlatformBySlug($platformType['name'], $platformSlug); + + if ($platformData === null) { + continue; + } + + $platformsData[] = [ + 'podcast_id' => $this->podcast->id, + 'type' => $platformType['name'], + 'slug' => $platformSlug, + 'link_url' => $platform->getAttribute($platformType['account_url_key']), + 'account_id' => $platform->getAttribute($platformType['account_id_key']), + 'is_visible' => 0, + ]; + } + + $platformModel->savePlatforms($this->podcast->id, $platformType['name'], $platformsData); + CLI::showProgress(false); + } + } + + private function importEpisodes(): void + { + helper('text'); + + $itemsCount = count($this->podcastFeed->channel->items); + $this->importTask->setEpisodesCount($itemsCount); + + CLI::write('Adding episodes - ' . $itemsCount . ' episodes'); + + $htmlConverter = new HtmlConverter(); + + $importedGUIDs = $this->getImportedGUIDs($this->podcast->id); + + $currEpisodesStep = 0; // for progress + $episodesNewlyImported = 0; + $episodesAlreadyImported = 0; + + // insert episodes in reverse order, from the last item in the list to the first + foreach (array_reverse($this->podcastFeed->channel->items) as $key => $item) { + CLI::showProgress(++$currEpisodesStep, $itemsCount); + + if (in_array($item->guid->getValue(), $importedGUIDs, true)) { + // do not import item if already imported + // (check that item with guid has already been inserted) + $this->importTask->setEpisodesAlreadyImported(++$episodesAlreadyImported); + continue; + } + + $db = db_connect(); + $db->transStart(); + + $location = null; + if ($item->podcast_location->getValue() !== null) { + $location = new Location( + $item->podcast_location->getValue(), + $item->podcast_location->getAttribute('geo'), + $item->podcast_location->getAttribute('osm'), + ); + } + + if (($showNotes = $this->getShowNotes($item)) === null) { + $db->transRollback(); + throw new Exception('Missing item show notes. Please include a tag to item ' . $key); + } + + $coverUrl = $this->getCoverUrl($item); + + $parentalAdvisory = null; + if ($item->itunes_explicit->getValue() !== null) { + $parentalAdvisory = $item->itunes_explicit->getValue() ? 'explicit' : 'clean'; + } + + $episode = new Episode([ + 'created_by' => $this->user->id, + 'updated_by' => $this->user->id, + 'podcast_id' => $this->podcast->id, + 'title' => $item->title->getValue(), + 'slug' => slugify((string) $item->title->getValue(), 120) . '-' . strtolower( + random_string('alnum', 5), + ), + 'guid' => $item->guid->getValue(), + 'audio' => download_file( + $item->enclosure->getAttribute('url'), + $item->enclosure->getAttribute('type'), + ), + 'description_markdown' => $htmlConverter->convert($showNotes), + 'description_html' => $showNotes, + 'cover' => $coverUrl ? download_file($coverUrl) : null, + 'parental_advisory' => $parentalAdvisory, + 'number' => $item->itunes_episode->getValue(), + 'season_number' => $item->itunes_season->getValue(), + 'type' => $item->itunes_episodeType->getValue(), + 'is_blocked' => $item->itunes_block->getValue(), + 'location' => $location, + 'is_premium' => $this->podcast->is_premium_by_default, + 'published_at' => $item->pubDate->getValue(), + ]); + + $episodeModel = new EpisodeModel(); + + if (! ($episodeId = $episodeModel->insert($episode, true))) { + $db->transRollback(); + throw new Exception(print_r($episodeModel->errors(), true)); + } + + $this->importEpisodePersons($episodeId, $item->podcast_persons); + + $this->importTask->setEpisodesNewlyImported(++$episodesNewlyImported); + + $db->transComplete(); + } + } + + /** + * @return string[] + */ + private function getImportedGUIDs(int $podcastId): array + { + $result = new EpisodeModel() + ->builder() + ->select('guid') + ->where('podcast_id', $podcastId) + ->get() + ->getResultArray(); + + return array_map(static fn (array $element) => $element['guid'], $result); + } + + /** + * @param PodcastPerson[] $persons + */ + private function importEpisodePersons(int $episodeId, array $persons): void + { + foreach ($persons as $person) { + $fullName = $person->getValue(); + $personModel = new PersonModel(); + $newPersonId = null; + if (($newPerson = $personModel->getPerson($fullName)) instanceof Person) { + $newPersonId = $newPerson->id; + } else { + $newPerson = new Person([ + 'created_by' => $this->user->id, + 'updated_by' => $this->user->id, + 'full_name' => $fullName, + 'unique_name' => slugify($fullName), + 'information_url' => $person->getAttribute('href'), + 'avatar' => download_file((string) $person->getAttribute('img')), + ]); + + if (! ($newPersonId = $personModel->insert($newPerson))) { + throw new Exception(print_r($personModel->errors(), true)); + } + } + + $personGroup = $person->getAttribute('group'); + $personRole = $person->getAttribute('role'); + + // set default group and role if taxonomy is not found + $personGroupSlug = 'cast'; + $personRoleSlug = 'host'; + + if (array_key_exists(strtolower((string) $personGroup), ReversedTaxonomy::$taxonomy)) { + $personGroup = ReversedTaxonomy::$taxonomy[strtolower((string) $personGroup)]; + $personGroupSlug = $personGroup['slug']; + + if (array_key_exists(strtolower((string) $personRole), $personGroup['roles'])) { + $personRoleSlug = $personGroup['roles'][strtolower((string) $personRole)]['slug']; + } + } + + $episodePersonModel = new PersonModel(); + if (! $episodePersonModel->addEpisodePerson( + $this->podcast->id, + $episodeId, + $newPersonId, + $personGroupSlug, + $personRoleSlug, + )) { + throw new Exception(print_r($episodePersonModel->errors(), true)); + } + } + } + + private function error(string $message): void + { + if ($this->importTask instanceof PodcastImportTask) { + $this->importTask->fail($message); + } + + CLI::error('[Error] ' . $message); + } +} diff --git a/modules/PodcastImport/Config/Routes.php b/modules/PodcastImport/Config/Routes.php new file mode 100644 index 00000000..ede5be52 --- /dev/null +++ b/modules/PodcastImport/Config/Routes.php @@ -0,0 +1,50 @@ +group( + config('Admin') + ->gateway, + [ + 'namespace' => 'Modules\PodcastImport\Controllers', + ], + static function ($routes): void { + $routes->get('imports', 'PodcastImportController::list', [ + 'as' => 'all-podcast-imports', + 'filter' => 'permission:podcasts.import', + ]); + $routes->get('imports/add', 'PodcastImportController::addToQueueView', [ + 'as' => 'podcast-imports-add', + 'filter' => 'permission:podcasts.import', + ]); + $routes->post('imports/add', 'PodcastImportController::addToQueueAction', [ + 'filter' => 'permission:podcasts.import', + ]); + $routes->get('imports/(:segment)/(:alpha)', 'PodcastImportController::taskAction/$1/$2', [ + 'as' => 'podcast-imports-task-action', + 'filter' => 'permission:podcasts.import', + ]); + + $routes->group('podcasts/(:num)', static function ($routes): void { + $routes->get('imports', 'PodcastImportController::podcastList/$1', [ + 'as' => 'podcast-imports', + 'filter' => 'permission:podcast$1.manage-import', + ]); + $routes->get('sync-feeds', 'PodcastImportController::syncImport/$1', [ + 'as' => 'podcast-imports-sync', + 'filter' => 'permission:podcast$1.manage-import', + ]); + $routes->post('sync-feeds', 'PodcastImportController::syncImportAttempt/$1', [ + 'as' => 'podcast-imports-sync', + 'filter' => 'permission:podcast$1.manage-import', + ]); + }); + }, +); diff --git a/modules/PodcastImport/Controllers/PodcastImportController.php b/modules/PodcastImport/Controllers/PodcastImportController.php new file mode 100644 index 00000000..d15037d2 --- /dev/null +++ b/modules/PodcastImport/Controllers/PodcastImportController.php @@ -0,0 +1,219 @@ +setHtmlHead(lang('Podcast.all_imports')); + return view('import/queue', [ + 'podcastImportsQueue' => get_import_tasks(), + ]); + } + + public function podcastList(int $podcastId): string + { + if (! ($podcast = new PodcastModel()->getPodcastById($podcastId)) instanceof Podcast) { + throw PageNotFoundException::forPageNotFound(); + } + + helper('podcast_import'); + + $this->setHtmlHead(lang('Podcast.all_imports')); + replace_breadcrumb_params([ + 0 => $podcast->at_handle, + ]); + return view('import/podcast_queue', [ + 'podcast' => $podcast, + 'podcastImportsQueue' => get_import_tasks($podcast->handle), + ]); + } + + public function addToQueueView(): string + { + helper(['form', 'misc']); + + $languageOptions = new LanguageModel() + ->getLanguageOptions(); + $categoryOptions = new CategoryModel() + ->getCategoryOptions(); + + $data = [ + 'languageOptions' => $languageOptions, + 'categoryOptions' => $categoryOptions, + 'browserLang' => get_browser_language($this->request->getServer('HTTP_ACCEPT_LANGUAGE')), + ]; + + $this->setHtmlHead(lang('Podcast.import')); + return view('import/add_to_queue', $data); + } + + public function addToQueueAction(): RedirectResponse + { + $rules = [ + 'handle' => 'required|regex_match[/^[a-zA-Z0-9\_]{1,32}$/]', + 'imported_feed_url' => 'required|valid_url_strict', + 'language' => 'required', + 'category' => 'required', + ]; + + if (! $this->validate($rules)) { + return redirect() + ->back() + ->withInput() + ->with('errors', $this->validator->getErrors()); + } + + $validData = $this->validator->getValidated(); + + // TODO: check that handle is not already in use + + $importTask = new PodcastImportTask([ + 'handle' => $validData['handle'], + 'feed_url' => $validData['imported_feed_url'], + 'language' => $validData['language'], + 'category' => $validData['category'], + 'status' => TaskStatus::Queued, + 'created_by' => user_id(), + 'updated_by' => user_id(), + 'created_at' => Time::now(), + 'updated_at' => Time::now(), + ]); + + $importTask->save(); + + return redirect()->route('all-podcast-imports') + ->with('message', lang('PodcastImport.messages.importTaskQueued')); + } + + public function syncImport(int $podcastId): string + { + if (! ($podcast = new PodcastModel()->getPodcastById($podcastId)) instanceof Podcast) { + throw PageNotFoundException::forPageNotFound(); + } + + helper('form'); + + $this->setHtmlHead(lang('PodcastImport.syncForm.title')); + replace_breadcrumb_params([ + 0 => $podcast->at_handle, + ]); + return view('import/podcast_sync', [ + 'podcast' => $podcast, + ]); + } + + public function syncImportAttempt(int $podcastId): RedirectResponse + { + if (! ($podcast = new PodcastModel()->getPodcastById($podcastId)) instanceof Podcast) { + throw PageNotFoundException::forPageNotFound(); + } + + $rules = [ + 'feed_url' => 'valid_url_strict', + ]; + + if (! $this->validate($rules)) { + return redirect() + ->back() + ->withInput() + ->with('errors', $this->validator->getErrors()); + } + + $validData = $this->validator->getValidated(); + + // create update task in podcastImport + $importTask = new PodcastImportTask([ + 'handle' => $podcast->handle, + 'feed_url' => $validData['feed_url'], + 'language' => $podcast->language_code, + 'category' => $podcast->category_id, + 'status' => TaskStatus::Queued, + 'created_by' => user_id(), + 'updated_by' => user_id(), + 'created_at' => Time::now(), + 'updated_at' => Time::now(), + ]); + + $importTask->save(); + + return redirect()->route('podcast-imports', [$podcastId]) + ->with('message', lang('PodcastImport.messages.syncTaskQueued')); + } + + public function taskAction(string $taskId, string $action): RedirectResponse + { + /** @var array $importQueue */ + $importQueue = service('settings') + ->get('Import.queue') ?? []; + + if (! array_key_exists($taskId, $importQueue)) { + throw PageNotFoundException::forPageNotFound(); + } + + $importTask = $importQueue[$taskId]; + switch ($action) { + case 'cancel': + if ($importTask->status === TaskStatus::Running || $importTask->status === TaskStatus::Queued) { + $importTask->cancel(); + + return redirect()->back() + ->with('message', lang('PodcastImport.messages.canceled')); + } + + return redirect()->back() + ->with('error', lang('PodcastImport.messages.notRunning')); + case 'retry': + if ($importTask->status === TaskStatus::Running || $importTask->status === TaskStatus::Queued) { + return redirect()->back() + ->with('error', lang('PodcastImport.messages.alreadyRunning')); + } + + $newImportTask = new PodcastImportTask([ + 'handle' => $importTask->handle, + 'feed_url' => $importTask->feed_url, + 'language' => $importTask->language, + 'category' => $importTask->category, + 'status' => TaskStatus::Queued, + 'created_by' => user_id(), + 'updated_by' => user_id(), + 'created_at' => Time::now(), + 'updated_at' => Time::now(), + ]); + + $newImportTask->save(); + + return redirect()->back() + ->with('message', lang('PodcastImport.messages.retried')); + case 'delete': + $importTask->delete(); + return redirect()->back() + ->with('message', lang('PodcastImport.messages.deleted')); + default: + throw new Exception('Task action ' . $action . ' was not implemented'); + } + } +} diff --git a/modules/PodcastImport/Entities/PodcastImportTask.php b/modules/PodcastImport/Entities/PodcastImportTask.php new file mode 100644 index 00000000..b0de2f95 --- /dev/null +++ b/modules/PodcastImport/Entities/PodcastImportTask.php @@ -0,0 +1,242 @@ + $data + */ + public function __construct(array $data) + { + parent::__construct($data); + + if (! array_key_exists('id', $data)) { + $this->id = md5($this->feed_url . Time::now()); + } + } + + public function getProgress(): float + { + if ($this->episodes_count === null) { + return 0; + } + + return $this->episodes_imported / $this->episodes_count; + } + + public function getEpisodesImported(): int + { + return $this->episodes_newly_imported + $this->episodes_already_imported; + } + + public function setEpisodesNewlyImported(int $episodesImported): void + { + $this->episodes_newly_imported = $episodesImported; + + $this->save(); + } + + public function setEpisodesAlreadyImported(int $episodesImported): void + { + $this->episodes_already_imported = $episodesImported; + + $this->save(); + } + + public function setEpisodesCount(int $episodesCount): void + { + $this->episodes_count = $episodesCount; + + $this->save(); + } + + public function getDuration(): int + { + if ($this->duration === null && $this->started_at !== null && $this->ended_at !== null) { + $this->duration = ($this->started_at->difference($this->ended_at)) + ->getSeconds(); + } + + return $this->duration; + } + + public function start(): void + { + if ($this->process_id !== null) { + throw new Exception('Task is already running!'); + } + + $processId = getmypid(); + + if ($processId === false) { + throw new Exception('Error Processing Request', 1); + } + + $this->process_id = $processId; + $this->started_at = Time::now(); + $this->status = TaskStatus::Running; + $this->save(); + + service('settings') + ->set('Import.current', $this->handle); + } + + public function pass(): void + { + $this->process_id = null; + $this->ended_at = Time::now(); + $this->status = TaskStatus::Passed; + + $this->save(); + + service('settings') + ->forget('Import.current'); + } + + public function cancel(): void + { + if ($this->status !== TaskStatus::Running && $this->status !== TaskStatus::Queued) { + throw new Exception('Task can only be canceled if running or queued.'); + } + + if ($this->isProcessRunning()) { + // kill process + $isProcessKilled = posix_kill($this->process_id, 9); + + if (! $isProcessKilled) { + throw new Exception('Something wrong happened, process could not be killed.'); + } + } + + $this->process_id = null; + $this->status = TaskStatus::Canceled; + $this->ended_at = Time::now(); + $this->save(); + } + + public function delete(): void + { + if ($this->isProcessRunning()) { + $this->cancel(); + } + + $importQueue = service('settings') + ->get('Import.queue') ?? []; + + if ($importQueue === []) { + return; + } + + unset($importQueue[$this->id]); + + service('settings') + ->set('Import.queue', $importQueue); + } + + public function fail(string $message): void + { + $this->error = $message; + + $this->status = TaskStatus::Failed; + $this->ended_at = Time::now(); + $this->save(); + + service('settings') + ->forget('Import.current'); + } + + public function save(): void + { + $importQueue = service('settings') + ->get('Import.queue') ?? []; + + $now = Time::now(); + + if (! array_key_exists($this->id, $importQueue)) { + $this->created_at = $now; + } + + $this->updated_at = $now; + + $importQueue[$this->id] = $this; + + service('settings') + ->set('Import.queue', $importQueue); + } + + public function syncWithProcess(): void + { + if ($this->status !== TaskStatus::Running && $this->process_id !== null) { + $this->process_id = null; + $this->save(); + return; + } + + if ($this->status === TaskStatus::Running && $this->process_id === null) { + $this->fail('Running task has no process id set.'); + return; + } + + if (! $this->isProcessRunning()) { + $this->fail('Process was killed.'); + return; + } + } + + private function isProcessRunning(): bool + { + if ($this->process_id === null) { + return false; + } + + return posix_getpgid($this->process_id) !== false; + } +} diff --git a/modules/PodcastImport/Entities/TaskStatus.php b/modules/PodcastImport/Entities/TaskStatus.php new file mode 100644 index 00000000..53246d0f --- /dev/null +++ b/modules/PodcastImport/Entities/TaskStatus.php @@ -0,0 +1,14 @@ +get('Import.queue') ?? []; + + if ($podcastHandle !== null) { + $podcastImportsQueue = array_filter( + $podcastImportsQueue, + static fn (PodcastImportTask $importTask): bool => $importTask->handle === $podcastHandle, + ); + } + + usort($podcastImportsQueue, static function (PodcastImportTask $a, PodcastImportTask $b): int { + if ($a->status === $b->status) { + return $a->created_at->isAfter($b->created_at) ? -1 : 1; + } + + if ($a->status === TaskStatus::Running) { + return -1; + } + + if ($a->status === TaskStatus::Queued && $b->status !== TaskStatus::Running) { + return -1; + } + + return $a->created_at->isAfter($b->created_at) ? -1 : 1; + }); + + return $podcastImportsQueue; + } +} diff --git a/modules/PodcastImport/Language/ar/PodcastImport.php b/modules/PodcastImport/Language/ar/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/ar/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/br/PodcastImport.php b/modules/PodcastImport/Language/br/PodcastImport.php new file mode 100644 index 00000000..c4f01d81 --- /dev/null +++ b/modules/PodcastImport/Language/br/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Enporzhiañ', + 'text' => 'Emañ {podcastTitle} o vezañ enporzhiet.', + 'cta' => 'Gwelet stad an enporzh', + ], + 'old_podcast_section_title' => 'Ar podkast da vezañ enporzhiet', + 'old_podcast_legal_disclaimer_title' => 'Menegoù lezennel', + 'old_podcast_legal_disclaimer' => + 'Bezit sur oc\'h aotreet da enporzhiañ ar podkast-mañ a-raok en ober. Eilañ ha skignañ ur podkast hep kaout an aotre a zo heñvel ouzh dambreziñ ha gallout a reer bezañ kaset d\'al lez-varn.', + 'imported_feed_url' => 'URL ar wazh', + 'imported_feed_url_hint' => 'Rankout a ra ar wazh bezañ er furmad xml pe rss.', + 'new_podcast_section_title' => 'Ar podkast nevez', + 'lock_import' => + 'Gwarezet eo ar wazh-mañ. Ne c\'hallit ket enporzhiañ anezhi. Ma\'z oc\'h ar perc\'henn·ez, dibrennit anezhi war ar savenn orin.', + 'submit' => 'Ouzhpennañ an enporzh d\'al lost', + 'queue' => [ + 'status' => [ + 'label' => 'Statud', + 'queued' => 'el lost', + 'queued_hint' => 'Emañ an enporzh el lost.', + 'canceled' => 'nullet', + 'canceled_hint' => 'Nullet eo bet an enporzh.', + 'running' => 'war ar stern', + 'running_hint' => 'Emañ an enporzh war ar stern.', + 'failed' => 'c\'hwitet', + 'failed_hint' => 'An enporzh n\'eo ket bet graet: fazi skript.', + 'passed' => 'berzh', + 'passed_hint' => 'Enporzhiet eo bet gant berzh!', + ], + 'feed' => 'Gwazh', + 'duration' => 'Padelezh an enporzh', + 'imported_episodes' => 'Rannoù enporzhiet', + 'imported_episodes_hint' => '{newlyImportedCount} enporzhiet nevez zo, {alreadyImportedCount} enporzhiet en holl.', + 'actions' => [ + 'cancel' => 'Nullañ', + 'retry' => 'Klask en-dro', + 'delete' => 'Dilemel', + ], + ], + 'syncForm' => [ + 'title' => 'Sinkronekaat ar gwazhoù', + 'feed_url' => 'URL ar wazh', + 'feed_url_hint' => 'URL ar wazh ho peus c\'hoant da sinkronekaat ouzh ar podkast-mañ.', + 'submit' => 'Ouzhpennañ d\'al lost', + ], + 'messages' => [ + 'canceled' => 'An enporzh zo bet nullet gant berzh!', + 'notRunning' => 'Ne c\'haller ket nullañ an enporzh rak n\'eo ket krog al labour.', + 'alreadyRunning' => 'Emañ an enporzh war ar stern. Gallout a rit nullañ anezhañ a-raok klask en-dro.', + 'retried' => 'Ouzhpennet eo bet an enporzh d\'al lost, klasket e vo en-dro dindan berr!', + 'deleted' => 'An enporzh zo bet dilamet gant berzh!', + 'importTaskQueued' => 'Un enporzh nevez zo bet ouzhpennet d\'al lost, kroget e vo dindan berr!', + 'syncTaskQueued' => 'Un enporzh nevez zo bet ouzhpennet d\'al lost, kroget e vo ar sinkronekaat dindan berr!', + ], +]; diff --git a/modules/PodcastImport/Language/ca/PodcastImport.php b/modules/PodcastImport/Language/ca/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/ca/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/da/PodcastImport.php b/modules/PodcastImport/Language/da/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/da/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/de/PodcastImport.php b/modules/PodcastImport/Language/de/PodcastImport.php new file mode 100644 index 00000000..0cfa75f6 --- /dev/null +++ b/modules/PodcastImport/Language/de/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'Importstatus anzeigen', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'in Warteschlangen', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'abgebrochen', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'läuft', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'fehlgeschlagen', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'bestanden', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Abbrechen', + 'retry' => 'Erneut versuchen', + 'delete' => 'Löschen', + ], + ], + 'syncForm' => [ + 'title' => 'Feeds synchronisieren', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Zur Warteschlange hinzufügen', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/el/PodcastImport.php b/modules/PodcastImport/Language/el/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/el/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/en/PodcastImport.php b/modules/PodcastImport/Language/en/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/en/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/es/PodcastImport.php b/modules/PodcastImport/Language/es/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/es/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/eu/PodcastImport.php b/modules/PodcastImport/Language/eu/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/eu/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/fa/PodcastImport.php b/modules/PodcastImport/Language/fa/PodcastImport.php new file mode 100644 index 00000000..cf5f4592 --- /dev/null +++ b/modules/PodcastImport/Language/fa/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'درون‌ریزی', + 'text' => '{podcastTitle} دارد درون‌ریخته می‌شود.', + 'cta' => 'دیدن وضعیت درون‌ریزی', + ], + 'old_podcast_section_title' => 'پادکست برای درون‌ریزی', + 'old_podcast_legal_disclaimer_title' => 'سلب مسئولیت حقوقی', + 'old_podcast_legal_disclaimer' => + 'پیش از درون‌ریزی مطمئن شوید حقوق این پادکست را دارید. رونوشت و پخش یک پادکست بدون حقوق مناسب دزدی دریایی حساب شده و قابل پیگرد است.', + 'imported_feed_url' => 'نشانی خوراک', + 'imported_feed_url_hint' => 'خورام باید در قالب xml یا rss باشد.', + 'new_podcast_section_title' => 'پادکست جدید', + 'lock_import' => + 'این خوراک محافظت شده است. نمی‌توانید درون‌ریزیش کنید. اگر مالکش هستید، روی بن‌سازهٔ اصلی قفل‌گشاییش کنید.', + 'submit' => 'افزودن درون‌ریزی به صف', + 'queue' => [ + 'status' => [ + 'label' => 'وضعیت', + 'queued' => 'صف شده', + 'queued_hint' => 'وظیفهٔ درون‌ریزی منتظر پردازش است.', + 'canceled' => 'لغو شده', + 'canceled_hint' => 'وظیفهٔ درون‌ریزی لغو شد.', + 'running' => 'درحال اجرا', + 'running_hint' => 'وظیفهٔ درون‌ریزی در حال پردازش است.', + 'failed' => 'شکست خورده', + 'failed_hint' => 'وظیفهٔ درون‌ریزی نتوانست کامل شود: شکست کدنوشته.', + 'passed' => 'قبول شده', + 'passed_hint' => 'وظیفهٔ درون‌ریزی با موفّقیت کامل شد!', + ], + 'feed' => 'خوراک', + 'duration' => 'طول درون‌ریزی', + 'imported_episodes' => 'قسمت‌های درون‌ریخته', + 'imported_episodes_hint' => '{newlyImportedCount} به تازگی درون‌ریخته. {alreadyImportedCount} از پیش درون‌ریخته.', + 'actions' => [ + 'cancel' => 'لغو', + 'retry' => 'تلاش دوباره', + 'delete' => 'حذف', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'وظیفهٔ درون‌ریزی با موفّقیت لغو شد!', + 'notRunning' => 'نمی‌توان وظیفهٔ درون‌ریزی را لغو کرد؛ چرا که در حال اجرا نیست.', + 'alreadyRunning' => 'وظیفهٔ درون‌ریزی در حال اجراست. پیش از تلاش دوباره باید لغوش کنید.', + 'retried' => 'وظیفهٔ درون‌ریزی صف شد. به زودی دوباره انجام خواهد شد!', + 'deleted' => 'وظیفهٔ درون‌ریزی با موفّقیت حذف شد!', + 'importTaskQueued' => 'وظیفه‌ای جدید صف شد. درون‌ریزی به زودی آغاز خواهد شد!', + 'syncTaskQueued' => 'وظیفهٔ درون‌ریزی جدیدی صف شد. هم‌گام سازی به زودی آغاز خواهد شد!', + ], +]; diff --git a/modules/PodcastImport/Language/fr-ca/PodcastImport.php b/modules/PodcastImport/Language/fr-ca/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/fr-ca/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/fr/PodcastImport.php b/modules/PodcastImport/Language/fr/PodcastImport.php new file mode 100644 index 00000000..c9187b7d --- /dev/null +++ b/modules/PodcastImport/Language/fr/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importation', + 'text' => '{podcastTitle} est actuellement en cours d\'import.', + 'cta' => 'Voir l\'état d\'import', + ], + 'old_podcast_section_title' => 'Le podcast à importer', + 'old_podcast_legal_disclaimer_title' => 'Avertissement légal', + 'old_podcast_legal_disclaimer' => + 'Assurez-vous d’être détenteur des droits du podcast avant de l’importer. Copier et diffuser un podcast sans en détenir les droits est assimilable à de la contrefaçon et est passible de poursuites.', + 'imported_feed_url' => 'Adresse du flux', + 'imported_feed_url_hint' => 'Le flux doit être au format xml ou rss.', + 'new_podcast_section_title' => 'Le nouveau podcast', + 'lock_import' => + 'Ce flux est protégé. Vous ne pouvez pas l’importer. Si en vous êtes le propriétaire, déverrouillez-le sur la plate-forme d’origine.', + 'submit' => 'Ajouter l\'import à la file d\'attente', + 'queue' => [ + 'status' => [ + 'label' => 'État', + 'queued' => 'en attente', + 'queued_hint' => 'L’import est dans la file d’attente.', + 'canceled' => 'annulé', + 'canceled_hint' => 'L’import a été annulé.', + 'running' => 'en cours', + 'running_hint' => 'L’import est en cours de traitement.', + 'failed' => 'échec', + 'failed_hint' => 'L’import n’a pas pu se terminer : échec du script.', + 'passed' => 'succès', + 'passed_hint' => 'L’import a été complété avec succès !', + ], + 'feed' => 'Flux', + 'duration' => 'Durée de l’import', + 'imported_episodes' => 'Épisodes importés', + 'imported_episodes_hint' => '{newlyImportedCount} vient d\'être importé, {alreadyImportedCount} a déjà été importé.', + 'actions' => [ + 'cancel' => 'Annuler', + 'retry' => 'Réessayer', + 'delete' => 'Supprimer', + ], + ], + 'syncForm' => [ + 'title' => 'Synchroniser les flux', + 'feed_url' => 'Adresse du flux', + 'feed_url_hint' => 'L\'URL du flux que vous voulez synchroniser avec le podcast actuel.', + 'submit' => 'Ajouter à la file d\'attente', + ], + 'messages' => [ + 'canceled' => 'L’import a été annulé avec succès !', + 'notRunning' => 'Impossible d’annuler l’import car il n’est pas en cours d’exécution.', + 'alreadyRunning' => 'L’import est déjà en cours d’exécution. Vous devez l’annuler avant de réessayer.', + 'retried' => 'L’import a été placé dans la file d’attente, il sera exécuté sous peu !', + 'deleted' => 'L’import a bien été supprimé !', + 'importTaskQueued' => 'Une nouvelle tâche a été mise attente, l’import va bientôt commencer !', + 'syncTaskQueued' => 'Une nouvelle tâche a été mise attente, la synchronisation va bientôt commencer !', + ], +]; diff --git a/modules/PodcastImport/Language/fr2/PodcastImport.php b/modules/PodcastImport/Language/fr2/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/fr2/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/gd/PodcastImport.php b/modules/PodcastImport/Language/gd/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/gd/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/gl/PodcastImport.php b/modules/PodcastImport/Language/gl/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/gl/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/id/PodcastImport.php b/modules/PodcastImport/Language/id/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/id/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/it/PodcastImport.php b/modules/PodcastImport/Language/it/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/it/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/ja/PodcastImport.php b/modules/PodcastImport/Language/ja/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/ja/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/kk/PodcastImport.php b/modules/PodcastImport/Language/kk/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/kk/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/ko/PodcastImport.php b/modules/PodcastImport/Language/ko/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/ko/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/nl/PodcastImport.php b/modules/PodcastImport/Language/nl/PodcastImport.php new file mode 100644 index 00000000..831e798b --- /dev/null +++ b/modules/PodcastImport/Language/nl/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importeren', + 'text' => '{podcastTitle} wordt momenteel geïmporteerd.', + 'cta' => 'Bekijk importstatus', + ], + 'old_podcast_section_title' => 'De te importeren podcast', + 'old_podcast_legal_disclaimer_title' => 'Wettelijke disclaimer', + 'old_podcast_legal_disclaimer' => + 'Zorg ervoor dat je de rechten hebt voor deze podcast voordat je deze importeert. Het kopiëren en uitzenden van een podcast zonder de juiste rechten is piraterij en kan vervolgd worden.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'De feed moet in xml of rss formaat zijn.', + 'new_podcast_section_title' => 'De nieuwe podcast', + 'lock_import' => + 'Deze feed is beschermd. U kunt deze niet importeren. Als u de eigenaar bent, ontgrendel het op het oorsprongsplatform.', + 'submit' => 'Import toevoegen aan wachtrij', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'in wachtrij', + 'queued_hint' => 'Import taak is in afwachting van verwerking.', + 'canceled' => 'geannuleerd', + 'canceled_hint' => 'Importtaak is geannuleerd.', + 'running' => 'actief', + 'running_hint' => 'Import taak wordt verwerkt.', + 'failed' => 'mislukt', + 'failed_hint' => 'Import taak kon niet voltooid worden: script mislukt.', + 'passed' => 'geslaagd', + 'passed_hint' => 'Import taak is succesvol voltooid!', + ], + 'feed' => 'Feed', + 'duration' => 'Duur van importeren', + 'imported_episodes' => 'Geïmporteerde afleveringen', + 'imported_episodes_hint' => '{newlyImportedCount} onlangs geïmporteerd, {alreadyImportedCount} reeds geïmporteerd.', + 'actions' => [ + 'cancel' => 'Annuleer', + 'retry' => 'Opnieuw proberen', + 'delete' => 'Verwijderen', + ], + ], + 'syncForm' => [ + 'title' => 'Feeds synchroniseren', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'De feed-URL die u wilt synchroniseren met de huidige podcast.', + 'submit' => 'Aan wachtrij toevoegen', + ], + 'messages' => [ + 'canceled' => 'Import taak is succesvol geannuleerd!', + 'notRunning' => 'De Import taak kan niet worden geannuleerd omdat deze niet wordt uitgevoerd.', + 'alreadyRunning' => 'Import taak is al actief. U kunt deze annuleren voordat u opnieuw probeert.', + 'retried' => 'Import taak is in de wachtrij gezet, deze zal binnenkort opnieuw worden geprobeerd!', + 'deleted' => 'Import taak is succesvol verwijderd!', + 'importTaskQueued' => 'Een nieuwe taak is in de wachtrij gezet, de import zal binnenkort beginnen!', + 'syncTaskQueued' => 'Een nieuwe taak is in de wachtrij gezet, de import zal binnenkort beginnen!', + ], +]; diff --git a/modules/PodcastImport/Language/nn-no/PodcastImport.php b/modules/PodcastImport/Language/nn-no/PodcastImport.php new file mode 100644 index 00000000..83cdd088 --- /dev/null +++ b/modules/PodcastImport/Language/nn-no/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importerer', + 'text' => '{podcastTitle} blir importert.', + 'cta' => 'Sjå status på importen', + ], + 'old_podcast_section_title' => 'Podkast å importera', + 'old_podcast_legal_disclaimer_title' => 'Juridisk ansvarsfråskriving', + 'old_podcast_legal_disclaimer' => + 'Syt for at du har rettane til podkasten før du importerer han. Å kopiera og kringkasta ein podkast utan løyve er ulovleg og straffbart.', + 'imported_feed_url' => 'URL til straumen', + 'imported_feed_url_hint' => 'Straumen må vera i xml- eller rss-format.', + 'new_podcast_section_title' => 'Den nye podkasten', + 'lock_import' => + 'Denne straumen er verna. Du kan ikkje importera han. Viss du er eigaren, må du låsa han opp på den originale plattforma.', + 'submit' => 'Legg importen i køen', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'i kø', + 'queued_hint' => 'Importjobben ventar på å bli utført.', + 'canceled' => 'avbrote', + 'canceled_hint' => 'Importjobben vart avbroten.', + 'running' => 'køyrer', + 'running_hint' => 'Utfører importoppgåva.', + 'failed' => 'mislukka', + 'failed_hint' => 'Greidde ikkje fullføra importen: skriptfeil.', + 'passed' => 'utført', + 'passed_hint' => 'Importen var vellukka.', + ], + 'feed' => 'Straum', + 'duration' => 'Kor lenge importen vara', + 'imported_episodes' => 'Importerte episodar', + 'imported_episodes_hint' => '{newlyImportedCount} nyss importerte, {alreadyImportedCount} allereie importerte.', + 'actions' => [ + 'cancel' => 'Avbryt', + 'retry' => 'Prøv på nytt', + 'delete' => 'Slett', + ], + ], + 'syncForm' => [ + 'title' => 'Synkroniser straumar', + 'feed_url' => 'URL til straumen', + 'feed_url_hint' => 'URL til straumen du vil synkronisera med denne podkasten.', + 'submit' => 'Legg til i køen', + ], + 'messages' => [ + 'canceled' => 'Importen vart avbroten.', + 'notRunning' => 'Kan ikkje avbryta importen, fordi han ikkje køyrer.', + 'alreadyRunning' => 'Importen er i gang. Du kan avbryta han før du prøver på nytt.', + 'retried' => 'Importjobben er lagt i køen, og vil bli prøvd på nytt straks.', + 'deleted' => 'Importjobben er sletta.', + 'importTaskQueued' => 'Ein ny jobb er lagd i køen, og importen startar straks.', + 'syncTaskQueued' => 'Ein ny importjobb er lagd i køen, og synkroniseringa startar straks.', + ], +]; diff --git a/modules/PodcastImport/Language/oc/PodcastImport.php b/modules/PodcastImport/Language/oc/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/oc/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/pl/PodcastImport.php b/modules/PodcastImport/Language/pl/PodcastImport.php new file mode 100644 index 00000000..525f4327 --- /dev/null +++ b/modules/PodcastImport/Language/pl/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importowanie', + 'text' => '{podcastTitle} jest obecnie importowany.', + 'cta' => 'Zobacz status importu', + ], + 'old_podcast_section_title' => 'Podcast do zaimportowania', + 'old_podcast_legal_disclaimer_title' => 'Zastrzeżenie prawne', + 'old_podcast_legal_disclaimer' => + 'Upewnij się, że masz prawa do tego podcastu zanim go zaimportujesz. Kopiowanie i nadawanie podcastu bez odpowiednich praw jest piractwem i podlega ściganiu.', + 'imported_feed_url' => 'Adres URL kanału', + 'imported_feed_url_hint' => 'Kanał musi być w formacie xml lub rss.', + 'new_podcast_section_title' => 'Nowy podcast', + 'lock_import' => + 'Ten kanał jest chroniony. Nie możesz go zaimportować. Jeśli jesteś jego właścicielem — usuń ochronę na platformie, z której pochodzi.', + 'submit' => 'Dodaj import do kolejki', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'w kolejce', + 'queued_hint' => 'Import czeka na przetworzenie.', + 'canceled' => 'anulowano', + 'canceled_hint' => 'Zadanie importu zostało anulowane.', + 'running' => 'w toku', + 'running_hint' => 'Import jest przetwarzany.', + 'failed' => 'niepowodzenie', + 'failed_hint' => 'Zadanie importu nie powiodło się: błąd skryptu.', + 'passed' => 'powodzenie', + 'passed_hint' => 'Zadanie importu zostało zakończone pomyślnie!', + ], + 'feed' => 'Kanał', + 'duration' => 'Czas trwania importu', + 'imported_episodes' => 'Zaimportowane odcinki', + 'imported_episodes_hint' => '{newlyImportedCount} nowoimportowanych, {alreadyImportedCount} już zaimportowanych.', + 'actions' => [ + 'cancel' => 'Anuluj', + 'retry' => 'Ponów', + 'delete' => 'Usuń', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronizuj kanały', + 'feed_url' => 'Adres URL kanału', + 'feed_url_hint' => 'URL kanału, który chcesz zsynchronizować z bieżącym podcastem.', + 'submit' => 'Dodaj do kolejki', + ], + 'messages' => [ + 'canceled' => 'Zadanie importu zostało pomyślnie anulowane!', + 'notRunning' => 'Nie można anulować importu, ponieważ nie jest on uruchomiony.', + 'alreadyRunning' => 'Zadanie importu jest już uruchomione. Możesz je anulować przed ponowną próbą.', + 'retried' => 'Zadanie importu zostało umieszczone w kolejce, zostanie ono wkrótce ponowione!', + 'deleted' => 'Zadanie importu zostało pomyślnie usunięte!', + 'importTaskQueued' => 'Nowe zadanie zostało dodane, import rozpocznie się wkrótce!', + 'syncTaskQueued' => 'Nowe zadanie importu zostało dodane, synchronizacja rozpocznie się wkrótce!', + ], +]; diff --git a/modules/PodcastImport/Language/pt-br/PodcastImport.php b/modules/PodcastImport/Language/pt-br/PodcastImport.php new file mode 100644 index 00000000..ae81ff90 --- /dev/null +++ b/modules/PodcastImport/Language/pt-br/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importando', + 'text' => '{podcastTitle} está sendo importado no momento.', + 'cta' => 'Ver status de importação', + ], + 'old_podcast_section_title' => 'O podcast para importar', + 'old_podcast_legal_disclaimer_title' => 'Aviso legal', + 'old_podcast_legal_disclaimer' => + 'Certifique-se de possuir os direitos para esse podcast antes de importá-lo. Copiar e transmitir um podcast sem os direitos adequados é pirataria e corre-se o risco de processo legal.', + 'imported_feed_url' => 'URL do feed', + 'imported_feed_url_hint' => 'O feed deve estar no formato xml ou rss.', + 'new_podcast_section_title' => 'O novo podcast', + 'lock_import' => + 'Este feed está protegido. Você não pode importá-lo. Se é o dono, desbloqueie-o na plataforma de origem.', + 'submit' => 'Adicionar importação à fila', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'na fila', + 'queued_hint' => 'Tarefa de importação está aguardando para ser processada.', + 'canceled' => 'cancelado', + 'canceled_hint' => 'Tarefa de importação foi cancelada.', + 'running' => 'executando', + 'running_hint' => 'Tarefa de importação está sendo processada.', + 'failed' => 'falhou', + 'failed_hint' => 'Tarefa de importação não pôde ser concluída: falha no script.', + 'passed' => 'aprovado', + 'passed_hint' => 'Tarefa de importação foi concluída com sucesso!', + ], + 'feed' => 'Feed', + 'duration' => 'Duração da importação', + 'imported_episodes' => 'Episódios importados', + 'imported_episodes_hint' => '{newlyImportedCount} recém importado, {alreadyImportedCount} já importado.', + 'actions' => [ + 'cancel' => 'Cancelar', + 'retry' => 'Tente novamente', + 'delete' => 'Excluir', + ], + ], + 'syncForm' => [ + 'title' => 'Sincronizar o feed', + 'feed_url' => 'URL do feed', + 'feed_url_hint' => 'A URL do feed que você deseja sincronizar com o podcast atual.', + 'submit' => 'Adicionar à fila', + ], + 'messages' => [ + 'canceled' => 'Tarefa de importação foi cancelada com sucesso!', + 'notRunning' => 'Não é possível cancelar a tarefa de importação, pois ela não está em execução.', + 'alreadyRunning' => 'Tarefa de Importação já está em execução. Você pode cancelá-la antes de tentar novamente.', + 'retried' => 'Tarefa de importação foi enfileirada, ela será repetida em breve!', + 'deleted' => 'Tarefa de importação foi cancelada com sucesso!', + 'importTaskQueued' => 'Uma nova tarefa foi colocada na fila, a importação será iniciada em breve!', + 'syncTaskQueued' => 'Uma nova tarefa de importação foi colocada na fila, a sincronização começará em breve!', + ], +]; diff --git a/modules/PodcastImport/Language/pt/PodcastImport.php b/modules/PodcastImport/Language/pt/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/pt/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/ro/PodcastImport.php b/modules/PodcastImport/Language/ro/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/ro/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/ru/PodcastImport.php b/modules/PodcastImport/Language/ru/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/ru/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/sk/PodcastImport.php b/modules/PodcastImport/Language/sk/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/sk/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/sr-latn/PodcastImport.php b/modules/PodcastImport/Language/sr-latn/PodcastImport.php new file mode 100644 index 00000000..504175b7 --- /dev/null +++ b/modules/PodcastImport/Language/sr-latn/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Uvoz', + 'text' => '{podcastTitle} se trenutno uvozi.', + 'cta' => 'Pogledaj status uvoza', + ], + 'old_podcast_section_title' => 'Podkast koji se uvozi', + 'old_podcast_legal_disclaimer_title' => 'Pravno odricanje od odgovornosti', + 'old_podcast_legal_disclaimer' => + 'Uverite se da posedujete prava za ovaj podkast pre nego što ga uvezete. Kopiranje i emitovanje podkasta bez odgovarajućih prava je piraterija i podložno je krivičnom gonjenju.', + 'imported_feed_url' => 'URL snabdevača', + 'imported_feed_url_hint' => 'Snabdevač mora biti u xml ili rss formatu.', + 'new_podcast_section_title' => 'Novi podkast', + 'lock_import' => + 'Ovaj snabdevač je zaštićen. Ne možete ga uvesti. Ukoliko ste vlasnik, otključajte snabdevač na originalnoj platformi na kojoj ste ga napravili.', + 'submit' => 'Dodaj uvoz na čekanje', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'čekanje', + 'queued_hint' => 'Zadatak uvoza čeka na obradu.', + 'canceled' => 'otkazano', + 'canceled_hint' => 'Zadatak uvoza je otkazan.', + 'running' => 'u toku', + 'running_hint' => 'Zadatak uvoza se procesuira.', + 'failed' => 'nije uspеlo', + 'failed_hint' => 'Zadatak uvoza nije mogao da se završi: greška skripte.', + 'passed' => 'pauzirano', + 'passed_hint' => 'Zadatak uvoza uspešno obavljen!', + ], + 'feed' => 'Snabdevač', + 'duration' => 'Trajanje uvoza', + 'imported_episodes' => 'Uvežene epizode', + 'imported_episodes_hint' => '{newlyImportedCount} novo uvežena, {alreadyImportedCount} već uveženih.', + 'actions' => [ + 'cancel' => 'Otkaži', + 'retry' => 'Pokušaj ponovo', + 'delete' => 'Obriši', + ], + ], + 'syncForm' => [ + 'title' => 'Sinhronizuj snabdevače', + 'feed_url' => 'URL snabdevača', + 'feed_url_hint' => 'URL veza snabdevača koju želite da sinhronizujete sa trenutnim podkastom.', + 'submit' => 'Dodaj u redosled', + ], + 'messages' => [ + 'canceled' => 'Zadatak uvoza uspešno otkazan!', + 'notRunning' => 'Nije moguće otkazati zadatak uvoza jer isti nije u toku.', + 'alreadyRunning' => 'Zadatak uvoza je u toku. Možete ga otkazati pre ponovnog pokušaja.', + 'retried' => 'Zadatak uvoza je na čekanju, biće pokušan ponovo uskoro!', + 'deleted' => 'Zadatak uvoza uspešno obrisan!', + 'importTaskQueued' => 'Novi zadatak je na čekanju, uvoz će krenuti uskoro!', + 'syncTaskQueued' => 'Novi zadatak uvoza je na čekanju, sinhronizacija će početi uskoro!', + ], +]; diff --git a/modules/PodcastImport/Language/sv/PodcastImport.php b/modules/PodcastImport/Language/sv/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/sv/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/uk/PodcastImport.php b/modules/PodcastImport/Language/uk/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/uk/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PodcastImport/Language/zh-hans/PodcastImport.php b/modules/PodcastImport/Language/zh-hans/PodcastImport.php new file mode 100644 index 00000000..ca714eec --- /dev/null +++ b/modules/PodcastImport/Language/zh-hans/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => '输入', + 'text' => '{podcastTitle} 正在导入中。', + 'cta' => '查看导入状态', + ], + 'old_podcast_section_title' => '要导入的播客', + 'old_podcast_legal_disclaimer_title' => '法律免责声明', + 'old_podcast_legal_disclaimer' => + '请确保您在导入之前拥有此播客的权限。 在没有权限的情况下复制和广播播客是盗版行为,可能受到起诉。', + 'imported_feed_url' => '摘要 URL', + 'imported_feed_url_hint' => '摘要必须是 xml 或 rss 格式。', + 'new_podcast_section_title' => '新播客', + 'lock_import' => + '该摘要受到保护。 您无法导入它。 如果您是所有者,请在源平台解锁。', + 'submit' => '添加导入到队列', + 'queue' => [ + 'status' => [ + 'label' => '状态', + 'queued' => '队列', + 'queued_hint' => '导入任务正在等待处理。', + 'canceled' => '已取消', + 'canceled_hint' => '导入任务已取消。', + 'running' => '运行中', + 'running_hint' => '导入任务正在处理中。', + 'failed' => '已失败', + 'failed_hint' => '导入任务无法完成:脚本失败。', + 'passed' => '已通过', + 'passed_hint' => '导入任务顺利完成!', + ], + 'feed' => '摘要', + 'duration' => '导入时长', + 'imported_episodes' => '导入剧集', + 'imported_episodes_hint' => '{newlyImportedCount} 新导入, {alreadyImportedCount} 已经导入。', + 'actions' => [ + 'cancel' => '取消', + 'retry' => '重试', + 'delete' => '删除', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => '导入任务已成功取消!', + 'notRunning' => '无法取消导入任务,因为它未运行。', + 'alreadyRunning' => '导入任务已在运行。 您可以在重试之前取消它。', + 'retried' => '导入任务已排队,稍后将重试!', + 'deleted' => '导入任务已成功删除!', + 'importTaskQueued' => '新任务已排队,导入即将开始!', + 'syncTaskQueued' => '新的导入任务已排队,即将开始同步!', + ], +]; diff --git a/modules/PodcastImport/Language/zh-hant/PodcastImport.php b/modules/PodcastImport/Language/zh-hant/PodcastImport.php new file mode 100644 index 00000000..cac52509 --- /dev/null +++ b/modules/PodcastImport/Language/zh-hant/PodcastImport.php @@ -0,0 +1,66 @@ + [ + 'disclaimer' => 'Importing', + 'text' => '{podcastTitle} is currently being imported.', + 'cta' => 'See import status', + ], + 'old_podcast_section_title' => 'The podcast to import', + 'old_podcast_legal_disclaimer_title' => 'Legal disclaimer', + 'old_podcast_legal_disclaimer' => + 'Make sure you own the rights for this podcast before importing it. Copying and broadcasting a podcast without the proper rights is piracy and is liable to prosecution.', + 'imported_feed_url' => 'Feed URL', + 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', + 'new_podcast_section_title' => 'The new podcast', + 'lock_import' => + 'This feed is protected. You cannot import it. If you are the owner, unlock it on the origin platform.', + 'submit' => 'Add import to queue', + 'queue' => [ + 'status' => [ + 'label' => 'Status', + 'queued' => 'queued', + 'queued_hint' => 'Import task is awaiting to be processed.', + 'canceled' => 'canceled', + 'canceled_hint' => 'Import task was canceled.', + 'running' => 'running', + 'running_hint' => 'Import task is being processed.', + 'failed' => 'failed', + 'failed_hint' => 'Import task could not complete: script failure.', + 'passed' => 'passed', + 'passed_hint' => 'Import task was completed successfully!', + ], + 'feed' => 'Feed', + 'duration' => 'Import duration', + 'imported_episodes' => 'Imported episodes', + 'imported_episodes_hint' => '{newlyImportedCount} newly imported, {alreadyImportedCount} already imported.', + 'actions' => [ + 'cancel' => 'Cancel', + 'retry' => 'Retry', + 'delete' => 'Delete', + ], + ], + 'syncForm' => [ + 'title' => 'Synchronize feeds', + 'feed_url' => 'Feed URL', + 'feed_url_hint' => 'The feed URL you want to synchronize with the current podcast.', + 'submit' => 'Add to queue', + ], + 'messages' => [ + 'canceled' => 'Import task has been successfully canceled!', + 'notRunning' => 'Cannot cancel Import Task as it is not running.', + 'alreadyRunning' => 'Import Task is already running. You may cancel it before retrying.', + 'retried' => 'Import task has been queued, it will be retried shortly!', + 'deleted' => 'Import task has been successfully deleted!', + 'importTaskQueued' => 'A new task has been queued, import will start shortly!', + 'syncTaskQueued' => 'A new import task has been queued, synchronization will start shortly!', + ], +]; diff --git a/modules/PremiumPodcasts/Config/Registrar.php b/modules/PremiumPodcasts/Config/Registrar.php new file mode 100644 index 00000000..17d66880 --- /dev/null +++ b/modules/PremiumPodcasts/Config/Registrar.php @@ -0,0 +1,22 @@ + + */ + public static function Filters(): array + { + return [ + 'aliases' => [ + 'podcast-unlock' => PodcastUnlockFilter::class, + ], + ]; + } +} diff --git a/modules/PremiumPodcasts/Config/Routes.php b/modules/PremiumPodcasts/Config/Routes.php index 2859b596..80d19586 100644 --- a/modules/PremiumPodcasts/Config/Routes.php +++ b/modules/PremiumPodcasts/Config/Routes.php @@ -4,7 +4,9 @@ declare(strict_types=1); namespace Modules\PremiumPodcasts\Config; -$routes = service('routes'); +use CodeIgniter\Router\RouteCollection; + +/** @var RouteCollection $routes */ $routes->addPlaceholder('podcastHandle', '[a-zA-Z0-9\_]{1,32}'); @@ -18,106 +20,95 @@ $routes->group( static function ($routes): void { $routes->group('podcasts/(:num)/subscriptions', static function ($routes): void { $routes->get('/', 'SubscriptionController::list/$1', [ - 'as' => 'subscription-list', - 'filter' => - 'permission:podcasts-view,podcast-manage_subscriptions', + 'as' => 'subscription-list', + 'filter' => 'permission:podcast$1.manage-subscriptions', ]); - $routes->get('add', 'SubscriptionController::add/$1', [ - 'as' => 'subscription-add', - 'filter' => 'permission:podcast-manage_subscriptions', + $routes->get('new', 'SubscriptionController::createView/$1', [ + 'as' => 'subscription-create', + 'filter' => 'permission:podcast$1.manage-subscriptions', ]); $routes->post( - 'add', - 'SubscriptionController::attemptAdd/$1', + 'new', + 'SubscriptionController::createAction/$1', [ - 'filter' => - 'permission:podcast-manage_subscriptions', + 'filter' => 'permission:podcast$1.manage-subscriptions', ], ); - $routes->post('save-link', 'SubscriptionController::attemptLinkSave/$1', [ - 'as' => 'subscription-link-save', - 'filter' => 'permission:podcast-manage_subscriptions', + $routes->post('save-link', 'SubscriptionController::linkSaveAction/$1', [ + 'as' => 'subscription-link-save', + 'filter' => 'permission:podcast$1.manage-subscriptions', ]); // Subscription $routes->group('(:num)', static function ($routes): void { $routes->get('/', 'SubscriptionController::view/$1/$2', [ - 'as' => 'subscription-view', - 'filter' => - 'permission:podcast-manage_subscriptions', + 'as' => 'subscription-view', + 'filter' => 'permission:podcast$1.manage-subscriptions', ]); $routes->get( 'edit', - 'SubscriptionController::edit/$1/$2', + 'SubscriptionController::editView/$1/$2', [ - 'as' => 'subscription-edit', - 'filter' => - 'permission:podcast-manage_subscriptions', + 'as' => 'subscription-edit', + 'filter' => 'permission:podcast$1.manage-subscriptions', ], ); $routes->post( 'edit', - 'SubscriptionController::attemptEdit/$1/$2', + 'SubscriptionController::editAction/$1/$2', [ - 'as' => 'subscription-edit', - 'filter' => - 'permission:podcast-manage_subscriptions', + 'as' => 'subscription-edit', + 'filter' => 'permission:podcast$1.manage-subscriptions', ], ); $routes->get( 'regenerate-token', 'SubscriptionController::regenerateToken/$1/$2', [ - 'as' => 'subscription-regenerate-token', - 'filter' => - 'permission:podcast-manage_subscriptions', - ] + 'as' => 'subscription-regenerate-token', + 'filter' => 'permission:podcast$1.manage-subscriptions', + ], ); $routes->get( 'suspend', 'SubscriptionController::suspend/$1/$2', [ - 'as' => 'subscription-suspend', - 'filter' => - 'permission:podcast-manage_subscriptions', + 'as' => 'subscription-suspend', + 'filter' => 'permission:podcast$1.manage-subscriptions', ], ); $routes->post( 'suspend', - 'SubscriptionController::attemptSuspend/$1/$2', + 'SubscriptionController::suspendAction/$1/$2', [ - 'filter' => - 'permission:podcast-manage_subscriptions', + 'filter' => 'permission:podcast$1.manage-subscriptions', ], ); $routes->get( 'resume', 'SubscriptionController::resume/$1/$2', [ - 'as' => 'subscription-resume', - 'filter' => - 'permission:podcast-manage_subscriptions', + 'as' => 'subscription-resume', + 'filter' => 'permission:podcast$1.manage-subscriptions', ], ); $routes->get( 'delete', 'SubscriptionController::delete/$1/$2', [ - 'as' => 'subscription-delete', - 'filter' => - 'permission:podcast-manage_subscriptions', + 'as' => 'subscription-delete', + 'filter' => 'permission:podcast$1.manage-subscriptions', ], ); $routes->post( 'delete', - 'SubscriptionController::attemptDelete/$1/$2', + 'SubscriptionController::deleteAction/$1/$2', [ - 'filter' => - 'permission:podcast-manage_subscriptions', + 'filter' => 'permission:podcast$1.manage-subscriptions', ], ); }); }); - } + }, ); $routes->group( @@ -126,14 +117,14 @@ $routes->group( 'namespace' => 'Modules\PremiumPodcasts\Controllers', ], static function ($routes): void { - $routes->get('unlock', 'LockController/$1', [ + $routes->get('unlock', 'LockController::index/$1', [ 'as' => 'premium-podcast-unlock', ]); - $routes->post('unlock', 'LockController::attemptUnlock/$1', [ + $routes->post('unlock', 'LockController::unlockAction/$1', [ 'as' => 'premium-podcast-unlock', ]); - $routes->get('lock', 'LockController::attemptLock/$1', [ + $routes->get('lock', 'LockController::lockAction/$1', [ 'as' => 'premium-podcast-lock', ]); - } + }, ); diff --git a/modules/PremiumPodcasts/Config/Services.php b/modules/PremiumPodcasts/Config/Services.php index c8fb03b4..9ca49df7 100644 --- a/modules/PremiumPodcasts/Config/Services.php +++ b/modules/PremiumPodcasts/Config/Services.php @@ -4,21 +4,23 @@ declare(strict_types=1); namespace Modules\PremiumPodcasts\Config; -use Config\Services as BaseService; +use CodeIgniter\Config\BaseService; use Modules\PremiumPodcasts\Models\SubscriptionModel; use Modules\PremiumPodcasts\PremiumPodcasts; class Services extends BaseService { - public static function premium_podcasts(?SubscriptionModel $subscriptionModel = null, bool $getShared = true) - { + public static function premium_podcasts( + ?SubscriptionModel $subscriptionModel = null, + bool $getShared = true, + ): PremiumPodcasts { if ($getShared) { return self::getSharedInstance('premium_podcasts', $subscriptionModel); } $premiumPodcasts = new PremiumPodcasts(); - $subscriptionModel ??= model(SubscriptionModel::class); + $subscriptionModel ??= model('SubscriptionModel'); return $premiumPodcasts ->setSubscriptionModel($subscriptionModel); diff --git a/modules/PremiumPodcasts/Controllers/LockController.php b/modules/PremiumPodcasts/Controllers/LockController.php index 68899039..b06efd09 100644 --- a/modules/PremiumPodcasts/Controllers/LockController.php +++ b/modules/PremiumPodcasts/Controllers/LockController.php @@ -34,7 +34,7 @@ class LockController extends BaseController throw PageNotFoundException::forPageNotFound(); } - if (($podcast = (new PodcastModel())->getPodcastByHandle($params[0])) === null) { + if (! ($podcast = new PodcastModel()->getPodcastByHandle($params[0])) instanceof Podcast) { throw PageNotFoundException::forPageNotFound(); } @@ -58,7 +58,7 @@ class LockController extends BaseController return view('podcast/unlock', $data); } - public function attemptUnlock(): RedirectResponse + public function unlockAction(): RedirectResponse { $rules = [ 'token' => 'required', @@ -70,7 +70,9 @@ class LockController extends BaseController ->with('errors', $this->validator->getErrors()); } - $token = (string) $this->request->getPost('token'); + $validData = $this->validator->getValidated(); + + $token = $validData['token']; // attempt unlocking the podcast with the token if (! $this->premiumPodcasts->unlock($this->podcast->handle, $token)) { @@ -83,12 +85,13 @@ class LockController extends BaseController $redirectURL = session('redirect_url') ?? site_url('/'); unset($_SESSION['redirect_url']); - return redirect()->to($redirectURL) + return redirect() + ->to($redirectURL) ->withCookies() ->with('message', lang('PremiumPodcasts.messages.unlockSuccess')); } - public function attemptLock(): RedirectResponse + public function lockAction(): RedirectResponse { $this->premiumPodcasts->lock($this->podcast->handle); diff --git a/modules/PremiumPodcasts/Controllers/SubscriptionController.php b/modules/PremiumPodcasts/Controllers/SubscriptionController.php index 055de5b8..7ddf669a 100644 --- a/modules/PremiumPodcasts/Controllers/SubscriptionController.php +++ b/modules/PremiumPodcasts/Controllers/SubscriptionController.php @@ -32,7 +32,7 @@ class SubscriptionController extends BaseController throw PageNotFoundException::forPageNotFound(); } - if (($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) === null) { + if (! ($podcast = new PodcastModel()->getPodcastById((int) $params[0])) instanceof Podcast) { throw PageNotFoundException::forPageNotFound(); } @@ -42,7 +42,9 @@ class SubscriptionController extends BaseController return $this->{$method}(); } - if (($this->subscription = (new SubscriptionModel())->getSubscriptionById((int) $params[1])) === null) { + if (! ($this->subscription = new SubscriptionModel()->getSubscriptionById( + (int) $params[1], + )) instanceof Subscription) { throw PageNotFoundException::forPageNotFound(); } @@ -57,13 +59,14 @@ class SubscriptionController extends BaseController helper('form'); + $this->setHtmlHead(lang('Subscription.podcast_subscriptions')); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $this->podcast->at_handle, ]); return view('subscription/list', $data); } - public function attemptLinkSave(): RedirectResponse + public function linkSaveAction(): RedirectResponse { $rules = [ 'subscription_link' => 'valid_url_strict|permit_empty', @@ -75,13 +78,15 @@ class SubscriptionController extends BaseController ->with('errors', $this->validator->getErrors()); } - if (($subscriptionLink = $this->request->getPost('subscription_link')) === '') { + $validData = $this->validator->getValidated(); + + if (($subscriptionLink = $validData['subscription_link']) === '') { service('settings') ->forget('Subscription.link', 'podcast:' . $this->podcast->id); return redirect()->route('subscription-list', [$this->podcast->id])->with( 'message', - lang('Subscription.messages.linkRemoveSuccess') + lang('Subscription.messages.linkRemoveSuccess'), ); } @@ -94,25 +99,26 @@ class SubscriptionController extends BaseController return redirect()->route('subscription-list', [$this->podcast->id])->with( 'message', - lang('Subscription.messages.linkSaveSuccess') + lang('Subscription.messages.linkSaveSuccess'), ); } public function view(): string { $data = [ - 'podcast' => $this->podcast, + 'podcast' => $this->podcast, 'subscription' => $this->subscription, ]; + $this->setHtmlHead(lang('Subscription.view', [$this->subscription->id])); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $this->podcast->at_handle, 1 => '#' . $this->subscription->id, ]); return view('subscription/view', $data); } - public function add(): string + public function createView(): string { helper('form'); @@ -120,13 +126,14 @@ class SubscriptionController extends BaseController 'podcast' => $this->podcast, ]; + $this->setHtmlHead(lang('Subscription.add', [esc($this->podcast->title)])); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $this->podcast->at_handle, ]); - return view('subscription/add', $data); + return view('subscription/create', $data); } - public function attemptAdd(): RedirectResponse + public function createAction(): RedirectResponse { helper('text'); @@ -142,8 +149,8 @@ class SubscriptionController extends BaseController $newSubscription = new Subscription([ 'podcast_id' => $this->podcast->id, - 'email' => $this->request->getPost('email'), - 'token' => hash('sha256', $rawToken = random_string('alnum', 8)), + 'email' => $this->request->getPost('email'), + 'token' => hash('sha256', $rawToken = random_string('alnum', 8)), 'expires_at' => $expiresAt, 'created_by' => user_id(), 'updated_by' => user_id(), @@ -170,13 +177,13 @@ class SubscriptionController extends BaseController ], $this->podcast->language_code)) ->setMessage(view('subscription/email/welcome', [ 'subscription' => $newSubscription, - 'token' => $rawToken, + 'token' => $rawToken, ]))->setMailType('html') ->send()) { $db->transRollback(); return redirect()->route('subscription-list', [$this->podcast->id])->with( 'errors', - [lang('Subscription.messages.addError'), $email->printDebugger([])] + [lang('Subscription.messages.addError'), $email->printDebugger([])], ); } @@ -186,7 +193,7 @@ class SubscriptionController extends BaseController 'message', lang('Subscription.messages.addSuccess', [ 'subscriber' => $newSubscription->email, - ]) + ]), ); } @@ -217,13 +224,13 @@ class SubscriptionController extends BaseController ->setSubject(lang('Subscription.emails.reset_subject', [], $this->podcast->language_code)) ->setMessage(view('subscription/email/reset', [ 'subscription' => $this->subscription, - 'token' => $rawToken, + 'token' => $rawToken, ]))->setMailType('html') ->send()) { $db->transRollback(); return redirect()->route('subscription-list', [$this->podcast->id])->with( 'errors', - [lang('Subscription.messages.regenerateTokenError'), $email->printDebugger([])] + [lang('Subscription.messages.regenerateTokenError'), $email->printDebugger([])], ); } @@ -233,27 +240,28 @@ class SubscriptionController extends BaseController 'message', lang('Subscription.messages.regenerateTokenSuccess', [ 'subscriber' => $this->subscription->email, - ]) + ]), ); } - public function edit(): string + public function editView(): string { helper('form'); $data = [ - 'podcast' => $this->podcast, + 'podcast' => $this->podcast, 'subscription' => $this->subscription, ]; + $this->setHtmlHead(lang('Subscription.edit', [esc($this->podcast->title)])); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $this->podcast->at_handle, 1 => '#' . $this->subscription->id, ]); return view('subscription/edit', $data); } - public function attemptEdit(): RedirectResponse + public function editAction(): RedirectResponse { $expiresAt = null; $expirationDate = $this->request->getPost('expiration_date'); @@ -291,7 +299,7 @@ class SubscriptionController extends BaseController $db->transRollback(); return redirect()->route('subscription-list', [$this->podcast->id])->with( 'errors', - [lang('Subscription.messages.editError'), $email->printDebugger([])] + [lang('Subscription.messages.editError'), $email->printDebugger([])], ); } @@ -301,7 +309,7 @@ class SubscriptionController extends BaseController 'message', lang('Subscription.messages.editSuccess', [ 'subscriber' => $this->subscription->email, - ]) + ]), ); } @@ -310,18 +318,19 @@ class SubscriptionController extends BaseController helper('form'); $data = [ - 'podcast' => $this->podcast, + 'podcast' => $this->podcast, 'subscription' => $this->subscription, ]; + $this->setHtmlHead(lang('Subscription.suspend')); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $this->podcast->at_handle, 1 => '#' . $this->subscription->id, ]); return view('subscription/suspend', $data); } - public function attemptSuspend(): RedirectResponse + public function suspendAction(): RedirectResponse { $db = db_connect(); $db->transStart(); @@ -346,7 +355,7 @@ class SubscriptionController extends BaseController $db->transRollback(); return redirect()->route('subscription-list', [$this->podcast->id])->with( 'errors', - [lang('Subscription.messages.suspendError'), $email->printDebugger([])] + [lang('Subscription.messages.suspendError'), $email->printDebugger([])], ); } @@ -356,7 +365,7 @@ class SubscriptionController extends BaseController 'messages', lang('Subscription.messages.suspendSuccess', [ 'subscriber' => $this->subscription->email, - ]) + ]), ); } @@ -386,7 +395,7 @@ class SubscriptionController extends BaseController $db->transRollback(); return redirect()->route('subscription-list', [$this->podcast->id])->with( 'errors', - [lang('Subscription.messages.resumeError'), $email->printDebugger([])] + [lang('Subscription.messages.resumeError'), $email->printDebugger([])], ); } @@ -396,7 +405,7 @@ class SubscriptionController extends BaseController 'message', lang('Subscription.messages.resumeSuccess', [ 'subscriber' => $this->subscription->email, - ]) + ]), ); } @@ -405,23 +414,25 @@ class SubscriptionController extends BaseController helper('form'); $data = [ - 'podcast' => $this->podcast, + 'podcast' => $this->podcast, 'subscription' => $this->subscription, ]; + $this->setHtmlHead(lang('Subscription.delete')); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $this->podcast->at_handle, 1 => '#' . $this->subscription->id, ]); return view('subscription/delete', $data); } - public function attemptDelete(): RedirectResponse + public function deleteAction(): RedirectResponse { $db = db_connect(); $db->transStart(); - (new SubscriptionModel())->delete($this->subscription->id); + new SubscriptionModel() + ->delete($this->subscription->id); /** @var Email $email */ $email = service('email'); @@ -435,7 +446,7 @@ class SubscriptionController extends BaseController $db->transRollback(); return redirect()->route('subscription-list', [$this->podcast->id])->with( 'errors', - [lang('Subscription.messages.deleteError'), $email->printDebugger([])] + [lang('Subscription.messages.deleteError'), $email->printDebugger([])], ); } @@ -445,7 +456,7 @@ class SubscriptionController extends BaseController 'messages', lang('Subscription.messages.deleteSuccess', [ 'subscriber' => $this->subscription->email, - ]) + ]), ); } } diff --git a/modules/PremiumPodcasts/Database/Migrations/2022-07-07-120000_add_subscriptions.php b/modules/PremiumPodcasts/Database/Migrations/2022-07-07-120000_add_subscriptions.php index ae4b91ab..57fa8dfd 100644 --- a/modules/PremiumPodcasts/Database/Migrations/2022-07-07-120000_add_subscriptions.php +++ b/modules/PremiumPodcasts/Database/Migrations/2022-07-07-120000_add_subscriptions.php @@ -10,50 +10,52 @@ declare(strict_types=1); namespace Modules\PremiumPodcasts\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddSubscriptions extends Migration +class AddSubscriptions extends BaseMigration { + #[Override] public function up(): void { $this->forge->addField([ 'id' => [ - 'type' => 'INT', - 'unsigned' => true, + 'type' => 'INT', + 'unsigned' => true, 'auto_increment' => true, ], 'podcast_id' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'email' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 255, ], 'token' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 64, ], 'status' => [ - 'type' => 'ENUM', + 'type' => 'ENUM', 'constraint' => ['active', 'suspended'], - 'default' => 'active', + 'default' => 'active', ], 'status_message' => [ - 'type' => 'VARCHAR', + 'type' => 'VARCHAR', 'constraint' => 255, - 'null' => true, + 'null' => true, ], 'expires_at' => [ 'type' => 'DATETIME', 'null' => true, ], 'created_by' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'updated_by' => [ - 'type' => 'INT', + 'type' => 'INT', 'unsigned' => true, ], 'created_at' => [ @@ -73,6 +75,7 @@ class AddSubscriptions extends Migration $this->forge->createTable('subscriptions'); } + #[Override] public function down(): void { $this->forge->dropTable('subscriptions'); diff --git a/modules/PremiumPodcasts/Entities/Subscription.php b/modules/PremiumPodcasts/Entities/Subscription.php index 3ea10454..aa8e32be 100644 --- a/modules/PremiumPodcasts/Entities/Subscription.php +++ b/modules/PremiumPodcasts/Entities/Subscription.php @@ -15,7 +15,6 @@ use App\Models\PodcastModel; use CodeIgniter\Entity\Entity; use CodeIgniter\I18n\Time; use Modules\Analytics\Models\AnalyticsPodcastBySubscriptionModel; -use RuntimeException; /** * @property int $id @@ -38,7 +37,7 @@ class Subscription extends Entity protected ?Podcast $podcast = null; /** - * @var string[] + * @var list */ protected $dates = ['expires_at', 'created_at', 'updated_at']; @@ -46,21 +45,19 @@ class Subscription extends Entity * @var array */ protected $casts = [ - 'id' => 'integer', - 'podcast_id' => 'integer', - 'email' => 'string', - 'token' => 'string', - 'status' => 'string', + 'id' => 'integer', + 'podcast_id' => 'integer', + 'email' => 'string', + 'token' => 'string', + 'status' => 'string', 'status_message' => '?string', - 'created_by' => 'integer', - 'updated_by' => 'integer', + 'created_by' => 'integer', + 'updated_by' => 'integer', ]; public function getStatus(): string { - return ($this->expires_at !== null && $this->expires_at->isBefore( - Time::now() - )) ? 'expired' : $this->attributes['status']; + return $this->expires_at->isBefore(Time::now()) ? 'expired' : $this->attributes['status']; } /** @@ -102,12 +99,9 @@ class Subscription extends Entity */ public function getPodcast(): ?Podcast { - if ($this->podcast_id === null) { - throw new RuntimeException('Subscription must have a podcast_id before getting podcast.'); - } - if (! $this->podcast instanceof Podcast) { - $this->podcast = (new PodcastModel())->getPodcastById($this->podcast_id); + $this->podcast = new PodcastModel() + ->getPodcastById($this->podcast_id); } return $this->podcast; @@ -115,9 +109,7 @@ class Subscription extends Entity public function getDownloadsLast3Months(): int { - return (new AnalyticsPodcastBySubscriptionModel())->getNumberOfDownloadsLast3Months( - $this->podcast_id, - $this->id - ); + return new AnalyticsPodcastBySubscriptionModel() + ->getNumberOfDownloadsLast3Months($this->podcast_id, $this->id); } } diff --git a/modules/PremiumPodcasts/Filters/PodcastUnlockFilter.php b/modules/PremiumPodcasts/Filters/PodcastUnlockFilter.php index 74468fa2..2f08a68b 100644 --- a/modules/PremiumPodcasts/Filters/PodcastUnlockFilter.php +++ b/modules/PremiumPodcasts/Filters/PodcastUnlockFilter.php @@ -4,88 +4,92 @@ declare(strict_types=1); namespace Modules\PremiumPodcasts\Filters; +use App\Entities\Episode; use App\Models\EpisodeModel; use CodeIgniter\Filters\FilterInterface; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Router\Router; -use Config\App; use Modules\PremiumPodcasts\PremiumPodcasts; -use Myth\Auth\Authentication\AuthenticationBase; +use Override; class PodcastUnlockFilter implements FilterInterface { /** * Verifies that a user is logged in, or redirects to login. * - * @param array|null $params + * @param string[]|null $arguments * - * @return mixed + * @return RequestInterface|ResponseInterface|string|null */ - public function before(RequestInterface $request, $params = null) + #[Override] + public function before(RequestInterface $request, $arguments = null) { if (! function_exists('is_unlocked')) { helper('premium_podcasts'); } - $current = (string) current_url(true) - ->setHost('') - ->setScheme('') - ->stripQuery('token'); - - $config = config(App::class); - if ($config->forceGlobalSecureRequests) { - // Remove "https:/" - $current = substr($current, 7); - } - /** @var Router $router */ $router = service('router'); $routerParams = $router->params(); if ($routerParams === []) { - return; + return null; } // no need to go through the unlock form if user is connected - /** @var AuthenticationBase $auth */ - $auth = service('authentication'); - if ($auth->isLoggedIn()) { - return; + if (auth()->loggedIn()) { + return null; } // Make sure this isn't already a premium podcast route - if ($current === route_to('premium-podcast-unlock', $routerParams[0])) { - return; + if (url_is((string) route_to('premium-podcast-unlock', $routerParams[0]))) { + return null; + } + + // expect 2 parameters (podcast handle and episode slug) + if (count($routerParams) < 2) { + return null; + } + + $episode = new EpisodeModel() + ->getEpisodeBySlug($routerParams[0], $routerParams[1]); + + if (! $episode instanceof Episode) { + return null; } // Make sure that public episodes are still accessible - if ($routerParams >= 2 && ($episode = (new EpisodeModel())->getEpisodeBySlug( - $routerParams[0], - $routerParams[1] - )) && ! $episode->is_premium) { - return; + if (! $episode->is_premium) { + return null; } // Episode should be embeddable even if it is premium - if ($current === route_to('embed', $episode->podcast->handle, $episode->slug)) { - return; + if (url_is((string) route_to('embed', $episode->podcast->handle, $episode->slug))) { + return null; } - // if podcast is locked then send to the unlock form /** @var PremiumPodcasts $premiumPodcasts */ $premiumPodcasts = service('premium_podcasts'); - if (! $premiumPodcasts->check($routerParams[0])) { - session()->set('redirect_url', current_url()); - - return redirect()->route('premium-podcast-unlock', [$routerParams[0]]); + if ($premiumPodcasts->check($routerParams[0])) { + return null; } + + // podcast is locked, send to the unlock form + session() + ->set('redirect_url', current_url()); + + return redirect()->route('premium-podcast-unlock', [$routerParams[0]]); } /** - * @param array|null $arguments + * @param list|null $arguments + * + * @return ResponseInterface|null */ - public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void + #[Override] + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { + return null; } } diff --git a/modules/PremiumPodcasts/Language/ar/Subscription.php b/modules/PremiumPodcasts/Language/ar/Subscription.php index f8af256f..e83f0cb2 100644 --- a/modules/PremiumPodcasts/Language/ar/Subscription.php +++ b/modules/PremiumPodcasts/Language/ar/Subscription.php @@ -34,7 +34,7 @@ return [ 'email' => 'Email', 'expiration_date' => 'Expiration date', 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', + 'submit_create' => 'Create subscription', 'submit_edit' => 'Edit subscription', ], 'form_link_add' => [ diff --git a/modules/PremiumPodcasts/Language/br/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/br/PremiumPodcasts.php index 18c0dd4e..42e897b6 100644 --- a/modules/PremiumPodcasts/Language/br/PremiumPodcasts.php +++ b/modules/PremiumPodcasts/Language/br/PremiumPodcasts.php @@ -9,26 +9,26 @@ declare(strict_types=1); */ return [ - 'podcast_is_premium' => 'Podcast contains premium episodes', - 'episode_is_premium' => 'Episode is premium, only available to premium subscribers', - 'unlock_episode' => 'This episode is for premium subscribers only. Click to unlock it!', - 'banner_unlock' => 'This podcast contains premium episodes, only available to premium subscribers.', - 'banner_lock' => 'Podcast is unlocked, enjoy the premium episodes!', - 'subscribe' => 'Subscribe', - 'lock' => 'Lock', - 'unlock' => 'Unlock', + 'podcast_is_premium' => 'Rannoù Premium a zo er podkast-mañ', + 'episode_is_premium' => 'Ur rann Premium eo, ne c\'hell bezañ gwelet nemet gant koumananterien·ezed Premium', + 'unlock_episode' => 'Evit koumananterien·ezed Premium eo ar rann-mañ. Klikit evit dibrennañ anezhi!', + 'banner_unlock' => 'Rannoù Premium a zo er podkast-mañ, ne c\'hellont bezañ gwelet nemet gant koumananterien·ezed Premium.', + 'banner_lock' => 'Dibrennet eo bet ar podkast. Plijadur deoc\'h gant ar rannoù Premium!', + 'subscribe' => 'Koumanantiñ', + 'lock' => 'Prennañ', + 'unlock' => 'Dibrennañ', 'unlock_form' => [ - 'title' => 'Premium content', - 'subtitle' => 'This podcast contains locked premium episodes! Do you have the key to unlock them?', - 'token' => 'Enter your key', - 'token_hint' => 'If you are subscribed to {podcastTitle}, you may copy the key that was sent to you via email and paste it here.', - 'submit' => 'Unlock all episodes!', - 'call_to_action' => 'Unlock all episodes of {podcastTitle}:', - 'subscribe_cta' => 'Subscribe now!', + 'title' => 'Endalc\'had Premium', + 'subtitle' => 'Rannoù Premium prennet zo er podkast-mañ. Ha ganeoc\'h emañ an alc\'hwez evit dibrennañ anezho?', + 'token' => 'Lakait hoc’h alc\'hwez', + 'token_hint' => 'Ma\'z oc\'h koumanantet ouzh {podcastTitle} e c\'hellit eilañ an alc’hwez a zo bet kaset deoc\'h dre bostel ha pegañ anezhañ amañ.', + 'submit' => 'Dibrennañ an holl rannoù!', + 'call_to_action' => 'Dibrennit holl rannoù {podcastTitle}:', + 'subscribe_cta' => 'Koumanantit bremañ!', ], 'messages' => [ - 'unlockSuccess' => 'Podcast was successfully unlocked! Enjoy the premium episodes!', - 'unlockBadAttempt' => 'Your key does not seem to be working…', - 'lockSuccess' => 'Podcast was successfully locked!', + 'unlockSuccess' => 'Dibrennet eo bet ar podkast gant berzh! Plijadur deoc\'h gant ar rannoù Premium!', + 'unlockBadAttempt' => 'Hoc\'h alc\'hwez n\'eo seblant ket bezañ mat…', + 'lockSuccess' => 'Dibrennet eo bet ar podkast gant berzh!', ], ]; diff --git a/modules/PremiumPodcasts/Language/br/Subscription.php b/modules/PremiumPodcasts/Language/br/Subscription.php index f8af256f..a956f60b 100644 --- a/modules/PremiumPodcasts/Language/br/Subscription.php +++ b/modules/PremiumPodcasts/Language/br/Subscription.php @@ -9,92 +9,92 @@ declare(strict_types=1); */ return [ - 'podcast_subscriptions' => 'Podcast subscriptions', - 'add' => 'New subscription', - 'view' => 'View subscription', - 'edit' => 'Edit subscription', - 'regenerate_token' => 'Regenerate token', - 'suspend' => 'Suspend subscription', - 'resume' => 'Resume subscription', - 'delete' => 'Delete subscription', + 'podcast_subscriptions' => 'Koumanantoù ar podkast', + 'add' => 'Koumanant nevez', + 'view' => 'Gwelet ar c\'houmanant', + 'edit' => 'Kemmañ ar c\'houmanant', + 'regenerate_token' => 'Azgenel ar jedouer', + 'suspend' => 'Ehanañ ar c\'houmanant', + 'resume' => 'Adkregiñ gant ar c\'houmanant', + 'delete' => 'Dilemel ar c\'houmanant', 'status' => [ - 'active' => 'Active', - 'suspended' => 'Suspended', - 'expired' => 'Expired', + 'active' => 'Bev', + 'suspended' => 'Ehanet', + 'expired' => 'Tremenet', ], 'list' => [ - 'number' => 'Number', - 'email' => 'Email', - 'expiration_date' => 'Expiration date', - 'unlimited' => 'Unlimited', - 'downloads' => 'Downloads', - 'status' => 'Status', + 'number' => 'Niverenn', + 'email' => 'Postel', + 'expiration_date' => 'Deiziad termen', + 'unlimited' => 'Didermen', + 'downloads' => 'Pellgargadennoù', + 'status' => 'Statud', ], 'form' => [ - 'email' => 'Email', - 'expiration_date' => 'Expiration date', - 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', - 'submit_edit' => 'Edit subscription', + 'email' => 'Postel', + 'expiration_date' => 'Deiziad termen', + 'expiration_date_hint' => 'An deiziad hag an eur ma vo tremenet ar c\'houmanant. Laoskit goullo evit ur c\'houmanant didermen.', + 'submit_create' => 'Krouiñ ur c\'houmanant', + 'submit_edit' => 'Kemmañ ar c\'houmanant', ], 'form_link_add' => [ - 'link' => 'Subscription page link', - 'link_hint' => 'This will add a call to action in the website inviting listeners to subscribe to the podcast.', - 'submit' => 'Save link', + 'link' => 'Liamm etrezek pajenn ar c\'houmanant', + 'link_hint' => 'Gant an dra-se e vo ouzhpennet d\'al lec\'hienn ur galv da ober evit pediñ ar selaouerien·ezed da goumanantiñ ouzh ar podkast.', + 'submit' => 'Enrollañ al liamm', ], 'suspend_form' => [ - 'disclaimer' => 'Suspending the subscription will restrict the subscriber from having access to the premium content. You will still be able to lift the suspension afterwards.', - 'reason' => 'Reason', - 'reason_placeholder' => 'Why are you suspending the subscription?', - "submit" => 'Suspend subscription', + 'disclaimer' => 'Ehanañ ar c\'houmanant a viro ouzh ar goumananterien·ezed da welet an endalc\'had Premium. Gallout a reoc\'h skarzhañ an ehan war-lerc\'h.', + 'reason' => 'Abeg', + 'reason_placeholder' => 'Perak ho peus c\'hoant da ehanañ ar c\'houmanant?', + "submit" => 'Ehanañ ar c\'houmanant', ], 'delete_form' => [ - 'disclaimer' => 'Deleting {subscriber}\'s subscription will remove all analytics data associated with it.', - 'understand' => 'I understand, remove the subscription permanently', - 'submit' => 'Remove subscription', + 'disclaimer' => 'Dilemel koumanant {subscriber} a skarzho an holl stadegoù stag outañ.', + 'understand' => 'Kompren a ran, dilemel ar c\'houmanant da vat', + 'submit' => 'Dilemel ar c\'houmanant', ], 'messages' => [ - 'addSuccess' => 'New subscription added! A welcome email was sent to {subscriber}.', - 'addError' => 'Subscription could not be added.', - 'editSuccess' => 'Subscription expiry date was updated! An email was sent to {subscriber}.', - 'editError' => 'Subscription could not be edited.', - 'regenerateTokenSuccess' => 'Token regenerated! An email was sent to {subscriber} with the new token.', - 'regenerateTokenError' => 'Token could not be regenerated.', - 'deleteSuccess' => 'Subscription was removed! An email was sent to {subscriber}.', - 'deleteError' => 'Subscription could not be removed.', - 'suspendSuccess' => 'Subscription was suspended! An email was sent to {subscriber}.', - 'suspendError' => 'Subscription could not be suspended.', - 'resumeSuccess' => 'Subscription was resumed! An email was sent to {subscriber}.', - 'resumeError' => 'Subscription could not be resumed.', - 'linkSaveSuccess' => 'Subscription link was saved successfully! It will appear in the website as a Call To Action!', - 'linkRemoveSuccess' => 'Subscription link was removed successfully!', + 'addSuccess' => 'Ur c\'houmanant nevez zo bet ouzhpennet! Ur postel evit degemer {subscriber} zo bet kaset.', + 'addError' => 'N\'eo ket bet ouzhpennet ar c\'houmanant.', + 'editSuccess' => 'Deiziad termen ar c\'houmanant zo bet nevesaet! Ur postel zo bet kaset da {subscriber}.', + 'editError' => 'N\'eo ket bet kemmet ar c\'houmanant.', + 'regenerateTokenSuccess' => 'Azganet eo bet ar jedouer! Kaset eo bet ar jedouer nevez da {subscriber} dre bostel.', + 'regenerateTokenError' => 'N\'eo ket bet gellet azgenel ar jedouer.', + 'deleteSuccess' => 'Dilamet eo bet ar c\'houmanant! Ur postel zo bet kaset da {subscriber}.', + 'deleteError' => 'N\'eo ket bet gellet dilemel ar c\'houmanant.', + 'suspendSuccess' => 'Ehanet eo bet ar c\'houmanant! Ur postel zo bet kaset da {subscriber}.', + 'suspendError' => 'N\'eo ket bet gellet ehanañ ar c\'houmanant.', + 'resumeSuccess' => 'Adkroget eo bet gant ar c\'houmanant! Ur postel zo bet kaset da {subscriber}.', + 'resumeError' => 'N\'eo ket bet gellet adkregiñ gant ar c\'houmanant.', + 'linkSaveSuccess' => 'Liamm ar c\'houmanant zo bet enrollet gant berzh! Diskouezet e vo war al lec\'hienn evel ur galv da ober!', + 'linkRemoveSuccess' => 'Dilamet eo bet liamm ar c\'houmanant gant berzh!', ], 'emails' => [ - 'greeting' => 'Hey,', - 'token' => 'Your token: {0}', - 'unique_feed_link' => 'Your unique feed link: {0}', - 'how_to_use' => 'How to use?', - 'two_ways' => 'You have two ways of unlocking the premium episodes:', - 'import_into_app' => 'Copy your unique feed url inside your favourite podcast app (import it as a private feed to prevent exposing your credentials).', - 'go_to_website' => 'Go to {podcastWebsite}\'s website and unlock the podcast with your token.', - 'welcome_subject' => 'Welcome to {podcastTitle}', - 'welcome' => 'You have subscribed to {podcastTitle}, thank you and welcome aboard!', - 'welcome_token_title' => 'Here are your credentials to unlock the podcast\'s premium episodes:', - 'welcome_expires' => 'Your subscription was set to expire on {0}.', - 'welcome_never_expires' => 'Your subscription was set to never expire.', - 'reset_subject' => 'Your token was reset!', - 'reset_token' => 'Your access to {podcastTitle} has been reset!', - 'reset_token_title' => 'New credentials have been generated for you to unlock the podcast\'s premium episodes:', - 'edited_subject' => 'Your subscription has been updated!', - 'edited_expires' => 'Your subscription for {podcastTitle} was set to expire on {expiresAt}.', - 'edited_never_expires' => 'Your subscription for {podcastTitle} was set to never expire!', - 'suspended_subject' => 'Your subscription has been suspended!', - 'suspended' => 'Your subscription for {podcastTitle} has been suspended! You can no longer access the podcast\'s premium episodes.', - 'suspended_reason' => 'That is for the following reason: {0}', - 'resumed_subject' => 'Your subscription has been resumed!', - 'resumed' => 'Your subscription for {podcastTitle} has been resumed! You may access the podcast\'s premium episodes again.', - 'deleted_subject' => 'Your subscription has been removed!', - 'deleted' => 'Your subscription for {podcastTitle} has been removed! You no longer have access to the podcast\'s premium episodes.', - 'footer' => '{castopod} hosted on {host}', + 'greeting' => 'Ata,', + 'token' => 'Ho jedouer: {0}', + 'unique_feed_link' => 'Liamm ho kwazh deoc\'h-c\'hwi: {0}', + 'how_to_use' => 'Penaos ober gantañ?', + 'two_ways' => 'Daou zoare zo evit dibrennañ rannoù Premium:', + 'import_into_app' => 'Eilit liamm ho kwazh personelaet e-barzh hoc\'h arload podkast karetañ (enporzhiit anezhañ evel ur wazh prevez kuit da lakaat war wel ho titouroù).', + 'go_to_website' => 'Kit da lec\'hienn {podcastWebsite} ha dibrennit ar podkast gant ho jedouer.', + 'welcome_subject' => 'Degemer mat war {podcastTitle}', + 'welcome' => 'Koumanantet oc\'h ouzh {podcastTitle}, mersi bras deoc\'h ha degemer mat!', + 'welcome_token_title' => 'Setu an titouroù evit dibrennañ rannoù Premium ar podkast:', + 'welcome_expires' => 'Arventennet eo bet ho koumanant da echuiñ d\'an/ar {0}.', + 'welcome_never_expires' => 'Arventennet eo bet ho koumanant evit bezañ didermen.', + 'reset_subject' => 'Azganet eo bet ho jedouer!', + 'reset_token' => 'Adderaouekaet eo bet ho moned da {podcastTitle}!', + 'reset_token_title' => 'Titouroù nevez zo bet ganet evit ma tibrennfec’h rannoù Premium ar podkast:', + 'edited_subject' => 'Nevesaet eo bet ho koumanant!', + 'edited_expires' => 'Arventennet eo bet ho koumanant ouzh {podcastTitle} da echuiñ d\'an/ar {expiresAt}.', + 'edited_never_expires' => 'Arventennet eo bet ho koumanant ouzh {podcastTitle} da vezañ didermen!', + 'suspended_subject' => 'Ehanet eo bet ho koumanant!', + 'suspended' => 'Ehanet eo bet ho koumanant ouzh {podcastTitle}! Ne c\'hallit ket gwelet rannoù Premium ar podkast ken.', + 'suspended_reason' => 'Evit an abeg da-heul: {0}', + 'resumed_subject' => 'Adkrog eo ho koumanant!', + 'resumed' => 'Adkrog eo ho koumanant ouzh {podcastTitle}! Gallout a rit ket gwelet rannoù Premium ar podkast en-dro.', + 'deleted_subject' => 'Dilamet eo bet ho koumanant!', + 'deleted' => 'Dilamet eo bet ho koumanant ouzh {podcastTitle}! Ne c\'hallit ket gwelet rannoù Premium ar podkast ken.', + 'footer' => '{castopod} herberc\'hiet war {host}', ], ]; diff --git a/modules/PremiumPodcasts/Language/ca/Subscription.php b/modules/PremiumPodcasts/Language/ca/Subscription.php index f8af256f..e83f0cb2 100644 --- a/modules/PremiumPodcasts/Language/ca/Subscription.php +++ b/modules/PremiumPodcasts/Language/ca/Subscription.php @@ -34,7 +34,7 @@ return [ 'email' => 'Email', 'expiration_date' => 'Expiration date', 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', + 'submit_create' => 'Create subscription', 'submit_edit' => 'Edit subscription', ], 'form_link_add' => [ diff --git a/modules/PremiumPodcasts/Language/nn-NO/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/da/PremiumPodcasts.php similarity index 100% rename from modules/PremiumPodcasts/Language/nn-NO/PremiumPodcasts.php rename to modules/PremiumPodcasts/Language/da/PremiumPodcasts.php diff --git a/modules/PremiumPodcasts/Language/nn-NO/Subscription.php b/modules/PremiumPodcasts/Language/da/Subscription.php similarity index 99% rename from modules/PremiumPodcasts/Language/nn-NO/Subscription.php rename to modules/PremiumPodcasts/Language/da/Subscription.php index f8af256f..e83f0cb2 100644 --- a/modules/PremiumPodcasts/Language/nn-NO/Subscription.php +++ b/modules/PremiumPodcasts/Language/da/Subscription.php @@ -34,7 +34,7 @@ return [ 'email' => 'Email', 'expiration_date' => 'Expiration date', 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', + 'submit_create' => 'Create subscription', 'submit_edit' => 'Edit subscription', ], 'form_link_add' => [ diff --git a/modules/PremiumPodcasts/Language/de/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/de/PremiumPodcasts.php index 18c0dd4e..178640ed 100644 --- a/modules/PremiumPodcasts/Language/de/PremiumPodcasts.php +++ b/modules/PremiumPodcasts/Language/de/PremiumPodcasts.php @@ -9,26 +9,26 @@ declare(strict_types=1); */ return [ - 'podcast_is_premium' => 'Podcast contains premium episodes', - 'episode_is_premium' => 'Episode is premium, only available to premium subscribers', - 'unlock_episode' => 'This episode is for premium subscribers only. Click to unlock it!', - 'banner_unlock' => 'This podcast contains premium episodes, only available to premium subscribers.', - 'banner_lock' => 'Podcast is unlocked, enjoy the premium episodes!', - 'subscribe' => 'Subscribe', - 'lock' => 'Lock', - 'unlock' => 'Unlock', + 'podcast_is_premium' => 'Podcast enthält Premium-Episoden', + 'episode_is_premium' => 'Diese Episode ist nur verfügbar für Premium-Abonnenten', + 'unlock_episode' => 'Diese Episode ist nur für Premium-Abonnenten. Klicke, um sie freizuschalten!', + 'banner_unlock' => 'Dieser Podcast enthält Premium-Episoden, nur verfügbar für Premium-Abonnenten.', + 'banner_lock' => 'Der Podcast ist freigeschaltet, viel Spaß mit den Premium-Episoden!', + 'subscribe' => 'Abonnieren', + 'lock' => 'Sperren', + 'unlock' => 'Entsperren', 'unlock_form' => [ - 'title' => 'Premium content', - 'subtitle' => 'This podcast contains locked premium episodes! Do you have the key to unlock them?', - 'token' => 'Enter your key', - 'token_hint' => 'If you are subscribed to {podcastTitle}, you may copy the key that was sent to you via email and paste it here.', - 'submit' => 'Unlock all episodes!', - 'call_to_action' => 'Unlock all episodes of {podcastTitle}:', - 'subscribe_cta' => 'Subscribe now!', + 'title' => 'Premium-Inhalt', + 'subtitle' => 'Dieser Podcast enthält gesperrte Premium-Episoden! Haben Sie einen Schlüssel, um diese freizuschalten?', + 'token' => 'Geben Sie Ihren Schlüssel ein', + 'token_hint' => 'Wenn Sie {podcastTitle} abonniert haben, können Sie hier den Schlüssel einfügen, den Sie per E-Mail erhalten haben.', + 'submit' => 'Alle Episoden freischalten!', + 'call_to_action' => 'Schalten Sie alle Episoden von {podcastTitle} frei:', + 'subscribe_cta' => 'Jetzt abonnieren!', ], 'messages' => [ - 'unlockSuccess' => 'Podcast was successfully unlocked! Enjoy the premium episodes!', - 'unlockBadAttempt' => 'Your key does not seem to be working…', - 'lockSuccess' => 'Podcast was successfully locked!', + 'unlockSuccess' => 'Der Podcast wurde erfolgreich entsperrt! Viel Spaß mit den Premium-Episoden!', + 'unlockBadAttempt' => 'Ihr Schlüssel scheint nicht zu funktionieren…', + 'lockSuccess' => 'Der Podcast wurde erfolgreich gesperrt!', ], ]; diff --git a/modules/PremiumPodcasts/Language/de/Subscription.php b/modules/PremiumPodcasts/Language/de/Subscription.php index f8af256f..67b91013 100644 --- a/modules/PremiumPodcasts/Language/de/Subscription.php +++ b/modules/PremiumPodcasts/Language/de/Subscription.php @@ -9,92 +9,92 @@ declare(strict_types=1); */ return [ - 'podcast_subscriptions' => 'Podcast subscriptions', - 'add' => 'New subscription', - 'view' => 'View subscription', - 'edit' => 'Edit subscription', - 'regenerate_token' => 'Regenerate token', - 'suspend' => 'Suspend subscription', - 'resume' => 'Resume subscription', - 'delete' => 'Delete subscription', + 'podcast_subscriptions' => 'Podcast-Abonnements', + 'add' => 'Neues Abonnement', + 'view' => 'Abonnement anzeigen', + 'edit' => 'Abonnement bearbeiten', + 'regenerate_token' => 'Token neu generieren', + 'suspend' => 'Abonnement unterbrechen', + 'resume' => 'Abonnement fortsetzen', + 'delete' => 'Abonnement löschen', 'status' => [ - 'active' => 'Active', - 'suspended' => 'Suspended', - 'expired' => 'Expired', + 'active' => 'Aktiv', + 'suspended' => 'Unterbrochen', + 'expired' => 'Abgelaufen', ], 'list' => [ - 'number' => 'Number', - 'email' => 'Email', - 'expiration_date' => 'Expiration date', - 'unlimited' => 'Unlimited', + 'number' => 'Nummer', + 'email' => 'E-Mail', + 'expiration_date' => 'Ablaufdatum', + 'unlimited' => 'Unbegrenzt', 'downloads' => 'Downloads', 'status' => 'Status', ], 'form' => [ - 'email' => 'Email', - 'expiration_date' => 'Expiration date', - 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', - 'submit_edit' => 'Edit subscription', + 'email' => 'E-Mail', + 'expiration_date' => 'Ablaufdatum', + 'expiration_date_hint' => 'Das Datum und die Uhrzeit, zu der das Abonnement abläuft. Leer lassen für ein unbegrenztes Abonnement.', + 'submit_create' => 'Abonnement einrichten', + 'submit_edit' => 'Abonnement bearbeiten', ], 'form_link_add' => [ - 'link' => 'Subscription page link', - 'link_hint' => 'This will add a call to action in the website inviting listeners to subscribe to the podcast.', - 'submit' => 'Save link', + 'link' => 'Link zur Abonnement-Seite', + 'link_hint' => 'Dies fügt einen CTA (Call to Action) zur Webseite hinzu, der Hörer dazu einlädt, den Podcast zu abonnieren.', + 'submit' => 'Link speichern', ], 'suspend_form' => [ - 'disclaimer' => 'Suspending the subscription will restrict the subscriber from having access to the premium content. You will still be able to lift the suspension afterwards.', - 'reason' => 'Reason', - 'reason_placeholder' => 'Why are you suspending the subscription?', - "submit" => 'Suspend subscription', + 'disclaimer' => 'Das Pausieren des Abonnements wird dem Abonnenten den Zugang zu den Premium-Inhalten einschränken. Sie können die Pausierung jederzeit wieder aufheben.', + 'reason' => 'Grund', + 'reason_placeholder' => 'Warum unterbrechen Sie Ihr Abonnement?', + "submit" => 'Abonnement unterbrechen', ], 'delete_form' => [ - 'disclaimer' => 'Deleting {subscriber}\'s subscription will remove all analytics data associated with it.', - 'understand' => 'I understand, remove the subscription permanently', - 'submit' => 'Remove subscription', + 'disclaimer' => 'Durch das Löschen des Abonnements von {subscriber} werden alle damit verbundenen Analysedaten entfernt.', + 'understand' => 'Ich verstehe, entferne das Abonnement dauerhaft', + 'submit' => 'Abonnement entfernen', ], 'messages' => [ - 'addSuccess' => 'New subscription added! A welcome email was sent to {subscriber}.', - 'addError' => 'Subscription could not be added.', - 'editSuccess' => 'Subscription expiry date was updated! An email was sent to {subscriber}.', - 'editError' => 'Subscription could not be edited.', - 'regenerateTokenSuccess' => 'Token regenerated! An email was sent to {subscriber} with the new token.', - 'regenerateTokenError' => 'Token could not be regenerated.', - 'deleteSuccess' => 'Subscription was removed! An email was sent to {subscriber}.', - 'deleteError' => 'Subscription could not be removed.', - 'suspendSuccess' => 'Subscription was suspended! An email was sent to {subscriber}.', - 'suspendError' => 'Subscription could not be suspended.', - 'resumeSuccess' => 'Subscription was resumed! An email was sent to {subscriber}.', - 'resumeError' => 'Subscription could not be resumed.', - 'linkSaveSuccess' => 'Subscription link was saved successfully! It will appear in the website as a Call To Action!', - 'linkRemoveSuccess' => 'Subscription link was removed successfully!', + 'addSuccess' => 'Neues Abonnement hinzugefügt! Eine Willkommens-E-Mail wurde an {subscriber} gesendet.', + 'addError' => 'Abonnement konnte nicht hinzugefügt werden.', + 'editSuccess' => 'Das Ablaufdatum des Abonnements wurde aktualisiert! Es wurde eine E-Mail an {subscriber} gesendet.', + 'editError' => 'Abonnement konnte nicht bearbeitet werden.', + 'regenerateTokenSuccess' => 'Der Schlüssel wurde neu generiert! Eine E-Mail mit dem neuen Schlüssel wurde an {subscriber} gesendet.', + 'regenerateTokenError' => 'Schlüssel konnte nicht neu generiert werden.', + 'deleteSuccess' => 'Das Abonnement wurde entfernt! Es wurde eine E-Mail an {subscriber} gesendet.', + 'deleteError' => 'Abonnement konnte nicht entfernt werden.', + 'suspendSuccess' => 'Das Abonnement wurde pausiert! Es wurde eine E-Mail an {subscriber} gesendet.', + 'suspendError' => 'Abonnement konnte nicht pausiert werden.', + 'resumeSuccess' => 'Das Abonnement wurde fortgesetzt! Es wurde eine E-Mail an {subscriber} gesendet.', + 'resumeError' => 'Abonnement konnte nicht fortgesetzt werden.', + 'linkSaveSuccess' => 'Der Abonnement-Link wurde erfolgreich gespeichert! Dieser wird als CTA (Call to Action) auf der Webseite erscheinen!', + 'linkRemoveSuccess' => 'Der Abonnement-Link wurde erfolgreich entfernt!', ], 'emails' => [ 'greeting' => 'Hey,', - 'token' => 'Your token: {0}', - 'unique_feed_link' => 'Your unique feed link: {0}', - 'how_to_use' => 'How to use?', - 'two_ways' => 'You have two ways of unlocking the premium episodes:', - 'import_into_app' => 'Copy your unique feed url inside your favourite podcast app (import it as a private feed to prevent exposing your credentials).', - 'go_to_website' => 'Go to {podcastWebsite}\'s website and unlock the podcast with your token.', - 'welcome_subject' => 'Welcome to {podcastTitle}', - 'welcome' => 'You have subscribed to {podcastTitle}, thank you and welcome aboard!', - 'welcome_token_title' => 'Here are your credentials to unlock the podcast\'s premium episodes:', - 'welcome_expires' => 'Your subscription was set to expire on {0}.', - 'welcome_never_expires' => 'Your subscription was set to never expire.', - 'reset_subject' => 'Your token was reset!', - 'reset_token' => 'Your access to {podcastTitle} has been reset!', - 'reset_token_title' => 'New credentials have been generated for you to unlock the podcast\'s premium episodes:', - 'edited_subject' => 'Your subscription has been updated!', - 'edited_expires' => 'Your subscription for {podcastTitle} was set to expire on {expiresAt}.', - 'edited_never_expires' => 'Your subscription for {podcastTitle} was set to never expire!', - 'suspended_subject' => 'Your subscription has been suspended!', - 'suspended' => 'Your subscription for {podcastTitle} has been suspended! You can no longer access the podcast\'s premium episodes.', - 'suspended_reason' => 'That is for the following reason: {0}', - 'resumed_subject' => 'Your subscription has been resumed!', - 'resumed' => 'Your subscription for {podcastTitle} has been resumed! You may access the podcast\'s premium episodes again.', - 'deleted_subject' => 'Your subscription has been removed!', - 'deleted' => 'Your subscription for {podcastTitle} has been removed! You no longer have access to the podcast\'s premium episodes.', - 'footer' => '{castopod} hosted on {host}', + 'token' => 'Ihr Token: {0}', + 'unique_feed_link' => 'Ihr eindeutiger Feed-Link: {0}', + 'how_to_use' => 'Wie nutzt man es?', + 'two_ways' => 'Sie haben zwei Möglichkeiten, die Premium-Episoden freizuschalten:', + 'import_into_app' => 'Kopieren Sie Ihre einmalige Feed-URL in Ihre Lieblings-Podcast-App (importieren sie diesen als privaten Feed, um Ihre Anmeldedaten geheim zu halten).', + 'go_to_website' => 'Gehen Sie zu der Webseite von {podcastWebsite} und entsperren Sie den Podcast mit Ihrem Schlüssel.', + 'welcome_subject' => 'Willkommen bei {podcastTitle}', + 'welcome' => 'Sie haben {podcastTitle} abonniert, vielen Dank und herzlich willkommen!', + 'welcome_token_title' => 'Hier sind Ihre Anmeldedaten, um die Premium-Episoden des Podcasts freizuschalten:', + 'welcome_expires' => 'Ihr Abonnement läuft am {0} ab.', + 'welcome_never_expires' => 'Ihr Abonnement läuft nicht ab.', + 'reset_subject' => 'Ihr Schlüssel wurde zurückgesetzt!', + 'reset_token' => 'Ihr Zugriff auf {podcastTitle} wurde zurückgesetzt!', + 'reset_token_title' => 'Es wurden neue Anmeldedaten generiert, um die Premium-Episoden des Podcasts freizuschalten:', + 'edited_subject' => 'Ihr Abonnement wurde aktualisiert!', + 'edited_expires' => 'Ihr Abonnement für {podcastTitle} läuft am {expiresAt} ab.', + 'edited_never_expires' => 'Ihr Abonnement für {podcastTitle} läuft nie ab!', + 'suspended_subject' => 'Ihr Abonnement wurde pausiert!', + 'suspended' => 'Ihr Abonnement für {podcastTitle} wurde pausiert! Sie können nun nicht mehr auf die Premium-Episoden des Podcasts zugreifen.', + 'suspended_reason' => 'Das ist aus dem folgenden Grund: {0}', + 'resumed_subject' => 'Ihr Abonnement wurde wieder aufgenommen!', + 'resumed' => 'Ihr Abonnement für {podcastTitle} wurde fortgesetzt! Sie können nun wieder auf die Premium-Episoden des Podcasts zugreifen.', + 'deleted_subject' => 'Ihr Abonnement wurde entfernt!', + 'deleted' => 'Ihr Abonnement für {podcastTitle} wurde entfernt! Sie können nun nicht mehr auf die Premium-Episoden des Podcasts zugreifen.', + 'footer' => '{castopod} betrieben auf {host}', ], ]; diff --git a/modules/PremiumPodcasts/Language/el/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/el/PremiumPodcasts.php index 18c0dd4e..2cdc8702 100644 --- a/modules/PremiumPodcasts/Language/el/PremiumPodcasts.php +++ b/modules/PremiumPodcasts/Language/el/PremiumPodcasts.php @@ -9,26 +9,26 @@ declare(strict_types=1); */ return [ - 'podcast_is_premium' => 'Podcast contains premium episodes', - 'episode_is_premium' => 'Episode is premium, only available to premium subscribers', - 'unlock_episode' => 'This episode is for premium subscribers only. Click to unlock it!', + 'podcast_is_premium' => 'Το Podcast περιέχει premium επεισόδια', + 'episode_is_premium' => 'Το επεισόδιο είναι premium, μόνο διαθέσιμο σε συνδρομητές premium', + 'unlock_episode' => 'Αυτό το επεισόδιο είναι μόνο για premium συνδρομητές. Κάντε κλικ για να το ξεκλειδώσετε!', 'banner_unlock' => 'This podcast contains premium episodes, only available to premium subscribers.', - 'banner_lock' => 'Podcast is unlocked, enjoy the premium episodes!', - 'subscribe' => 'Subscribe', - 'lock' => 'Lock', - 'unlock' => 'Unlock', + 'banner_lock' => 'Το Podcast είναι ξεκλειδωμένο, απολαύστε τα premium επεισόδια!', + 'subscribe' => 'Συνδρομή', + 'lock' => 'Κλείδωμα', + 'unlock' => 'Ξεκλείδωμα', 'unlock_form' => [ - 'title' => 'Premium content', - 'subtitle' => 'This podcast contains locked premium episodes! Do you have the key to unlock them?', - 'token' => 'Enter your key', + 'title' => 'Premium περιεχόμενο', + 'subtitle' => 'Αυτό το podcast περιέχει κλειδωμένα premium επεισόδια! Έχεις το κλειδί για να τα ξεκλειδώσεις;', + 'token' => 'Δώστε το κλειδί', 'token_hint' => 'If you are subscribed to {podcastTitle}, you may copy the key that was sent to you via email and paste it here.', - 'submit' => 'Unlock all episodes!', - 'call_to_action' => 'Unlock all episodes of {podcastTitle}:', - 'subscribe_cta' => 'Subscribe now!', + 'submit' => 'Ξεκλείδωμα όλων των επεισοδίων!', + 'call_to_action' => 'Ξεκλειδώστε όλα τα επεισόδια του {podcastTitle}:', + 'subscribe_cta' => 'Εγγραφείτε τώρα!', ], 'messages' => [ - 'unlockSuccess' => 'Podcast was successfully unlocked! Enjoy the premium episodes!', - 'unlockBadAttempt' => 'Your key does not seem to be working…', - 'lockSuccess' => 'Podcast was successfully locked!', + 'unlockSuccess' => 'Το Podcast ξεκλειδώθηκε με επιτυχία! Απολαύστε τα premium επεισόδια!', + 'unlockBadAttempt' => 'Το κλειδί σας δεν φαίνεται να λειτουργεί…', + 'lockSuccess' => 'Το Podcast κλειδώθηκε με επιτυχία!', ], ]; diff --git a/modules/PremiumPodcasts/Language/el/Subscription.php b/modules/PremiumPodcasts/Language/el/Subscription.php index f8af256f..a8f8f923 100644 --- a/modules/PremiumPodcasts/Language/el/Subscription.php +++ b/modules/PremiumPodcasts/Language/el/Subscription.php @@ -9,32 +9,32 @@ declare(strict_types=1); */ return [ - 'podcast_subscriptions' => 'Podcast subscriptions', - 'add' => 'New subscription', - 'view' => 'View subscription', - 'edit' => 'Edit subscription', - 'regenerate_token' => 'Regenerate token', - 'suspend' => 'Suspend subscription', - 'resume' => 'Resume subscription', - 'delete' => 'Delete subscription', + 'podcast_subscriptions' => 'Συνδρομές Podcast', + 'add' => 'Νέα συνδρομή', + 'view' => 'Προβολή συνδρομής', + 'edit' => 'Τροποποίηση συνδρομής', + 'regenerate_token' => 'Αναδημιουργία token', + 'suspend' => 'Αναστολή συνδρομής', + 'resume' => 'Συνέχιση συνδρομής', + 'delete' => 'Διαγραφή συνδρομής', 'status' => [ - 'active' => 'Active', - 'suspended' => 'Suspended', - 'expired' => 'Expired', + 'active' => 'Ενεργή', + 'suspended' => 'Έχει ανασταλεί', + 'expired' => 'Έληξε', ], 'list' => [ - 'number' => 'Number', - 'email' => 'Email', - 'expiration_date' => 'Expiration date', - 'unlimited' => 'Unlimited', - 'downloads' => 'Downloads', - 'status' => 'Status', + 'number' => 'Αριθμός', + 'email' => 'Ηλεκτρονική διεύθυνση', + 'expiration_date' => 'Ημερομηνία λήξης', + 'unlimited' => 'Απεριόριστα', + 'downloads' => 'Λήψεις', + 'status' => 'Κατάσταση', ], 'form' => [ 'email' => 'Email', - 'expiration_date' => 'Expiration date', + 'expiration_date' => 'Ημερομηνία λήξης', 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', + 'submit_create' => 'Create subscription', 'submit_edit' => 'Edit subscription', ], 'form_link_add' => [ diff --git a/modules/PremiumPodcasts/Language/en/Subscription.php b/modules/PremiumPodcasts/Language/en/Subscription.php index f8af256f..e83f0cb2 100644 --- a/modules/PremiumPodcasts/Language/en/Subscription.php +++ b/modules/PremiumPodcasts/Language/en/Subscription.php @@ -34,7 +34,7 @@ return [ 'email' => 'Email', 'expiration_date' => 'Expiration date', 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', + 'submit_create' => 'Create subscription', 'submit_edit' => 'Edit subscription', ], 'form_link_add' => [ diff --git a/modules/PremiumPodcasts/Language/es/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/es/PremiumPodcasts.php index 18c0dd4e..e7beaae7 100644 --- a/modules/PremiumPodcasts/Language/es/PremiumPodcasts.php +++ b/modules/PremiumPodcasts/Language/es/PremiumPodcasts.php @@ -9,26 +9,26 @@ declare(strict_types=1); */ return [ - 'podcast_is_premium' => 'Podcast contains premium episodes', - 'episode_is_premium' => 'Episode is premium, only available to premium subscribers', - 'unlock_episode' => 'This episode is for premium subscribers only. Click to unlock it!', - 'banner_unlock' => 'This podcast contains premium episodes, only available to premium subscribers.', - 'banner_lock' => 'Podcast is unlocked, enjoy the premium episodes!', - 'subscribe' => 'Subscribe', - 'lock' => 'Lock', - 'unlock' => 'Unlock', + 'podcast_is_premium' => 'Podcast contiene episodios premium', + 'episode_is_premium' => 'El episodio es premium, sólo disponible para los suscriptores premium', + 'unlock_episode' => 'Este episodio es sólo para suscriptores premium. ¡Haz clic para desbloquearlo!', + 'banner_unlock' => 'Este podcast contiene episodios premium, sólo disponible para los suscriptores premium.', + 'banner_lock' => 'Podcast desbloqueado, ¡disfruta de los episodios premium!', + 'subscribe' => 'Suscríbete', + 'lock' => 'Bloquear', + 'unlock' => 'Desbloquear', 'unlock_form' => [ - 'title' => 'Premium content', - 'subtitle' => 'This podcast contains locked premium episodes! Do you have the key to unlock them?', - 'token' => 'Enter your key', - 'token_hint' => 'If you are subscribed to {podcastTitle}, you may copy the key that was sent to you via email and paste it here.', - 'submit' => 'Unlock all episodes!', - 'call_to_action' => 'Unlock all episodes of {podcastTitle}:', - 'subscribe_cta' => 'Subscribe now!', + 'title' => 'Contenido premium', + 'subtitle' => '¡Este podcast contiene episodios premium bloqueados! ¿Tienes la clave para desbloquearlos?', + 'token' => 'Introduzca su clave', + 'token_hint' => 'Si está suscrito a {podcastTitle}, puede copiar la clave que le fue enviada por correo electrónico y pegarla aquí.', + 'submit' => '¡Desbloquea todos los episodios!', + 'call_to_action' => 'Desbloquea todos los episodios de {podcastTitle}:', + 'subscribe_cta' => '¡Suscríbete ahora!', ], 'messages' => [ - 'unlockSuccess' => 'Podcast was successfully unlocked! Enjoy the premium episodes!', - 'unlockBadAttempt' => 'Your key does not seem to be working…', - 'lockSuccess' => 'Podcast was successfully locked!', + 'unlockSuccess' => '¡Podcast desbloqueado con éxito! ¡Disfruta de los episodios premium!', + 'unlockBadAttempt' => 'Parece que tu clave no está funcionando…', + 'lockSuccess' => 'El Podcast fue bloqueado con éxito!', ], ]; diff --git a/modules/PremiumPodcasts/Language/es/Subscription.php b/modules/PremiumPodcasts/Language/es/Subscription.php index f8af256f..76df4e21 100644 --- a/modules/PremiumPodcasts/Language/es/Subscription.php +++ b/modules/PremiumPodcasts/Language/es/Subscription.php @@ -9,92 +9,92 @@ declare(strict_types=1); */ return [ - 'podcast_subscriptions' => 'Podcast subscriptions', - 'add' => 'New subscription', - 'view' => 'View subscription', - 'edit' => 'Edit subscription', - 'regenerate_token' => 'Regenerate token', - 'suspend' => 'Suspend subscription', - 'resume' => 'Resume subscription', - 'delete' => 'Delete subscription', + 'podcast_subscriptions' => 'Suscripciones de Podcast', + 'add' => 'Nueva suscripción', + 'view' => 'Ver suscripción', + 'edit' => 'Editar la suscripción', + 'regenerate_token' => 'Regenerar token', + 'suspend' => 'Suspender suscripción', + 'resume' => 'Reanudar suscripción', + 'delete' => 'Eliminar suscripción', 'status' => [ - 'active' => 'Active', - 'suspended' => 'Suspended', - 'expired' => 'Expired', + 'active' => 'Activo', + 'suspended' => 'Suspendido', + 'expired' => 'Caducado', ], 'list' => [ - 'number' => 'Number', - 'email' => 'Email', - 'expiration_date' => 'Expiration date', - 'unlimited' => 'Unlimited', - 'downloads' => 'Downloads', - 'status' => 'Status', + 'number' => 'Número', + 'email' => 'Correo electrónico', + 'expiration_date' => 'Fecha de expiración', + 'unlimited' => 'Ilimitado', + 'downloads' => 'Descargas', + 'status' => 'Estado', ], 'form' => [ - 'email' => 'Email', - 'expiration_date' => 'Expiration date', - 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', - 'submit_edit' => 'Edit subscription', + 'email' => 'Correo electrónico', + 'expiration_date' => 'Fecha de expiración', + 'expiration_date_hint' => 'La fecha y hora en que caduca la suscripción. Dejar en blanco para una suscripción ilimitada.', + 'submit_create' => 'Create subscription', + 'submit_edit' => 'Editar la suscripción', ], 'form_link_add' => [ - 'link' => 'Subscription page link', - 'link_hint' => 'This will add a call to action in the website inviting listeners to subscribe to the podcast.', - 'submit' => 'Save link', + 'link' => 'Enlace de página de suscripción', + 'link_hint' => 'Esto añadirá una llamada a la acción en el sitio web invitando a los oyentes a suscribirse al podcast.', + 'submit' => 'Guardar enlace', ], 'suspend_form' => [ - 'disclaimer' => 'Suspending the subscription will restrict the subscriber from having access to the premium content. You will still be able to lift the suspension afterwards.', - 'reason' => 'Reason', - 'reason_placeholder' => 'Why are you suspending the subscription?', - "submit" => 'Suspend subscription', + 'disclaimer' => 'Suspender la suscripción restringirá que el suscriptor tenga acceso al contenido premium. Aún podrá levantar la suspensión después.', + 'reason' => 'Motivo', + 'reason_placeholder' => '¿Por qué quieres detener tu suscripción?', + "submit" => 'Suspender suscripción', ], 'delete_form' => [ - 'disclaimer' => 'Deleting {subscriber}\'s subscription will remove all analytics data associated with it.', - 'understand' => 'I understand, remove the subscription permanently', - 'submit' => 'Remove subscription', + 'disclaimer' => 'Eliminar la suscripción de {subscriber} eliminará todos los datos analíticos asociados a ella.', + 'understand' => 'Entiendo, eliminar la suscripción permanentemente', + 'submit' => 'Eliminar Suscripción', ], 'messages' => [ - 'addSuccess' => 'New subscription added! A welcome email was sent to {subscriber}.', - 'addError' => 'Subscription could not be added.', - 'editSuccess' => 'Subscription expiry date was updated! An email was sent to {subscriber}.', - 'editError' => 'Subscription could not be edited.', - 'regenerateTokenSuccess' => 'Token regenerated! An email was sent to {subscriber} with the new token.', - 'regenerateTokenError' => 'Token could not be regenerated.', - 'deleteSuccess' => 'Subscription was removed! An email was sent to {subscriber}.', - 'deleteError' => 'Subscription could not be removed.', - 'suspendSuccess' => 'Subscription was suspended! An email was sent to {subscriber}.', - 'suspendError' => 'Subscription could not be suspended.', - 'resumeSuccess' => 'Subscription was resumed! An email was sent to {subscriber}.', - 'resumeError' => 'Subscription could not be resumed.', - 'linkSaveSuccess' => 'Subscription link was saved successfully! It will appear in the website as a Call To Action!', - 'linkRemoveSuccess' => 'Subscription link was removed successfully!', + 'addSuccess' => '¡Nueva suscripción añadida! Se ha enviado un correo electrónico de bienvenida a {subscriber}.', + 'addError' => 'La suscripción no pudo ser añadida.', + 'editSuccess' => '¡La fecha de caducidad de la suscripción ha sido actualizada! Se ha enviado un correo electrónico a {subscriber}.', + 'editError' => 'No se pudo editar la suscripción.', + 'regenerateTokenSuccess' => '¡Token regenerado! Un correo electrónico fue enviado a {subscriber} con el nuevo token.', + 'regenerateTokenError' => 'El token no se pudo regenerar.', + 'deleteSuccess' => '¡La suscripción ha sido eliminada! Se ha enviado un correo electrónico a {subscriber}.', + 'deleteError' => 'La suscripción no pudo ser eliminada.', + 'suspendSuccess' => '¡La suscripción ha sido suspendida! Se ha enviado un correo electrónico a {subscriber}.', + 'suspendError' => 'La suscripción no pudo ser suspendida.', + 'resumeSuccess' => 'La suscripción se ha reanudado! Se ha enviado un correo electrónico a {subscriber}.', + 'resumeError' => 'No se pudo reanudar la suscripción.', + 'linkSaveSuccess' => '¡El enlace de suscripción se ha guardado correctamente! ¡Aparecerá en el sitio web como una acción de llamada!', + 'linkRemoveSuccess' => '¡El enlace de suscripción se eliminó correctamente!', ], 'emails' => [ - 'greeting' => 'Hey,', - 'token' => 'Your token: {0}', - 'unique_feed_link' => 'Your unique feed link: {0}', - 'how_to_use' => 'How to use?', - 'two_ways' => 'You have two ways of unlocking the premium episodes:', - 'import_into_app' => 'Copy your unique feed url inside your favourite podcast app (import it as a private feed to prevent exposing your credentials).', - 'go_to_website' => 'Go to {podcastWebsite}\'s website and unlock the podcast with your token.', - 'welcome_subject' => 'Welcome to {podcastTitle}', - 'welcome' => 'You have subscribed to {podcastTitle}, thank you and welcome aboard!', - 'welcome_token_title' => 'Here are your credentials to unlock the podcast\'s premium episodes:', - 'welcome_expires' => 'Your subscription was set to expire on {0}.', - 'welcome_never_expires' => 'Your subscription was set to never expire.', - 'reset_subject' => 'Your token was reset!', - 'reset_token' => 'Your access to {podcastTitle} has been reset!', - 'reset_token_title' => 'New credentials have been generated for you to unlock the podcast\'s premium episodes:', - 'edited_subject' => 'Your subscription has been updated!', - 'edited_expires' => 'Your subscription for {podcastTitle} was set to expire on {expiresAt}.', - 'edited_never_expires' => 'Your subscription for {podcastTitle} was set to never expire!', - 'suspended_subject' => 'Your subscription has been suspended!', - 'suspended' => 'Your subscription for {podcastTitle} has been suspended! You can no longer access the podcast\'s premium episodes.', - 'suspended_reason' => 'That is for the following reason: {0}', - 'resumed_subject' => 'Your subscription has been resumed!', - 'resumed' => 'Your subscription for {podcastTitle} has been resumed! You may access the podcast\'s premium episodes again.', - 'deleted_subject' => 'Your subscription has been removed!', - 'deleted' => 'Your subscription for {podcastTitle} has been removed! You no longer have access to the podcast\'s premium episodes.', - 'footer' => '{castopod} hosted on {host}', + 'greeting' => 'Hola,', + 'token' => 'Tu token: {0}', + 'unique_feed_link' => 'Tu enlace de feed único: {0}', + 'how_to_use' => '¿Cómo se usa?', + 'two_ways' => 'Tienes dos maneras de desbloquear los episodios premium:', + 'import_into_app' => 'Copie su Url única dentro de su aplicación de podcast favorita (importe como un feed privado para evitar exponer sus credenciales).', + 'go_to_website' => 'Ve a la página web de {podcastWebsite} y desbloquea el podcast con tu token.', + 'welcome_subject' => 'Bienvenido a {podcastTitle}', + 'welcome' => 'Te has suscrito a {podcastTitle}, ¡gracias y bienvenido!', + 'welcome_token_title' => 'Aquí están tus credenciales para desbloquear los episodios premium del podcast:', + 'welcome_expires' => 'Sus suscripción caducará en {0}.', + 'welcome_never_expires' => 'Tu suscripción nunca expirará.', + 'reset_subject' => '¡Tu token ha sido restablecido!', + 'reset_token' => '¡Tu acceso a {podcastTitle} ha sido restablecido!', + 'reset_token_title' => 'Se han generado nuevas credenciales para desbloquear los episodios premium del podcast:', + 'edited_subject' => 'Su suscripción ha sido actualizada!', + 'edited_expires' => 'Su suscripción para {podcastTitle} caducará el {expiresAt}.', + 'edited_never_expires' => '¡Tu suscripción para {podcastTitle} nunca caducará!', + 'suspended_subject' => 'Tu suscripción ha sido suspendida!', + 'suspended' => '¡Tu suscripción para {podcastTitle} ha sido suspendida! Ya no puedes acceder a los episodios premium del podcast.', + 'suspended_reason' => 'Este es el siguiente motivo: {0}', + 'resumed_subject' => 'Hemos reactivado tu suscripción!', + 'resumed' => '¡Tu suscripción para {podcastTitle} ha sido reanudada! Puedes acceder de nuevo a los episodios premium del podcast.', + 'deleted_subject' => 'La suscripción ha sido eliminada!', + 'deleted' => '¡Tu suscripción para {podcastTitle} ha sido eliminada! Ya no tienes acceso a los episodios premium del podcast.', + 'footer' => '{castopod} alojado en {host}', ], ]; diff --git a/modules/PremiumPodcasts/Language/pt-BR/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/eu/PremiumPodcasts.php similarity index 100% rename from modules/PremiumPodcasts/Language/pt-BR/PremiumPodcasts.php rename to modules/PremiumPodcasts/Language/eu/PremiumPodcasts.php diff --git a/modules/PremiumPodcasts/Language/pt-BR/Subscription.php b/modules/PremiumPodcasts/Language/eu/Subscription.php similarity index 99% rename from modules/PremiumPodcasts/Language/pt-BR/Subscription.php rename to modules/PremiumPodcasts/Language/eu/Subscription.php index f8af256f..e83f0cb2 100644 --- a/modules/PremiumPodcasts/Language/pt-BR/Subscription.php +++ b/modules/PremiumPodcasts/Language/eu/Subscription.php @@ -34,7 +34,7 @@ return [ 'email' => 'Email', 'expiration_date' => 'Expiration date', 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', + 'submit_create' => 'Create subscription', 'submit_edit' => 'Edit subscription', ], 'form_link_add' => [ diff --git a/modules/PremiumPodcasts/Language/fa/Subscription.php b/modules/PremiumPodcasts/Language/fa/Subscription.php index f8af256f..e83f0cb2 100644 --- a/modules/PremiumPodcasts/Language/fa/Subscription.php +++ b/modules/PremiumPodcasts/Language/fa/Subscription.php @@ -34,7 +34,7 @@ return [ 'email' => 'Email', 'expiration_date' => 'Expiration date', 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', + 'submit_create' => 'Create subscription', 'submit_edit' => 'Edit subscription', ], 'form_link_add' => [ diff --git a/modules/PremiumPodcasts/Language/fr-ca/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/fr-ca/PremiumPodcasts.php new file mode 100644 index 00000000..18c0dd4e --- /dev/null +++ b/modules/PremiumPodcasts/Language/fr-ca/PremiumPodcasts.php @@ -0,0 +1,34 @@ + 'Podcast contains premium episodes', + 'episode_is_premium' => 'Episode is premium, only available to premium subscribers', + 'unlock_episode' => 'This episode is for premium subscribers only. Click to unlock it!', + 'banner_unlock' => 'This podcast contains premium episodes, only available to premium subscribers.', + 'banner_lock' => 'Podcast is unlocked, enjoy the premium episodes!', + 'subscribe' => 'Subscribe', + 'lock' => 'Lock', + 'unlock' => 'Unlock', + 'unlock_form' => [ + 'title' => 'Premium content', + 'subtitle' => 'This podcast contains locked premium episodes! Do you have the key to unlock them?', + 'token' => 'Enter your key', + 'token_hint' => 'If you are subscribed to {podcastTitle}, you may copy the key that was sent to you via email and paste it here.', + 'submit' => 'Unlock all episodes!', + 'call_to_action' => 'Unlock all episodes of {podcastTitle}:', + 'subscribe_cta' => 'Subscribe now!', + ], + 'messages' => [ + 'unlockSuccess' => 'Podcast was successfully unlocked! Enjoy the premium episodes!', + 'unlockBadAttempt' => 'Your key does not seem to be working…', + 'lockSuccess' => 'Podcast was successfully locked!', + ], +]; diff --git a/modules/PremiumPodcasts/Language/fr-ca/Subscription.php b/modules/PremiumPodcasts/Language/fr-ca/Subscription.php new file mode 100644 index 00000000..e83f0cb2 --- /dev/null +++ b/modules/PremiumPodcasts/Language/fr-ca/Subscription.php @@ -0,0 +1,100 @@ + 'Podcast subscriptions', + 'add' => 'New subscription', + 'view' => 'View subscription', + 'edit' => 'Edit subscription', + 'regenerate_token' => 'Regenerate token', + 'suspend' => 'Suspend subscription', + 'resume' => 'Resume subscription', + 'delete' => 'Delete subscription', + 'status' => [ + 'active' => 'Active', + 'suspended' => 'Suspended', + 'expired' => 'Expired', + ], + 'list' => [ + 'number' => 'Number', + 'email' => 'Email', + 'expiration_date' => 'Expiration date', + 'unlimited' => 'Unlimited', + 'downloads' => 'Downloads', + 'status' => 'Status', + ], + 'form' => [ + 'email' => 'Email', + 'expiration_date' => 'Expiration date', + 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', + 'submit_create' => 'Create subscription', + 'submit_edit' => 'Edit subscription', + ], + 'form_link_add' => [ + 'link' => 'Subscription page link', + 'link_hint' => 'This will add a call to action in the website inviting listeners to subscribe to the podcast.', + 'submit' => 'Save link', + ], + 'suspend_form' => [ + 'disclaimer' => 'Suspending the subscription will restrict the subscriber from having access to the premium content. You will still be able to lift the suspension afterwards.', + 'reason' => 'Reason', + 'reason_placeholder' => 'Why are you suspending the subscription?', + "submit" => 'Suspend subscription', + ], + 'delete_form' => [ + 'disclaimer' => 'Deleting {subscriber}\'s subscription will remove all analytics data associated with it.', + 'understand' => 'I understand, remove the subscription permanently', + 'submit' => 'Remove subscription', + ], + 'messages' => [ + 'addSuccess' => 'New subscription added! A welcome email was sent to {subscriber}.', + 'addError' => 'Subscription could not be added.', + 'editSuccess' => 'Subscription expiry date was updated! An email was sent to {subscriber}.', + 'editError' => 'Subscription could not be edited.', + 'regenerateTokenSuccess' => 'Token regenerated! An email was sent to {subscriber} with the new token.', + 'regenerateTokenError' => 'Token could not be regenerated.', + 'deleteSuccess' => 'Subscription was removed! An email was sent to {subscriber}.', + 'deleteError' => 'Subscription could not be removed.', + 'suspendSuccess' => 'Subscription was suspended! An email was sent to {subscriber}.', + 'suspendError' => 'Subscription could not be suspended.', + 'resumeSuccess' => 'Subscription was resumed! An email was sent to {subscriber}.', + 'resumeError' => 'Subscription could not be resumed.', + 'linkSaveSuccess' => 'Subscription link was saved successfully! It will appear in the website as a Call To Action!', + 'linkRemoveSuccess' => 'Subscription link was removed successfully!', + ], + 'emails' => [ + 'greeting' => 'Hey,', + 'token' => 'Your token: {0}', + 'unique_feed_link' => 'Your unique feed link: {0}', + 'how_to_use' => 'How to use?', + 'two_ways' => 'You have two ways of unlocking the premium episodes:', + 'import_into_app' => 'Copy your unique feed url inside your favourite podcast app (import it as a private feed to prevent exposing your credentials).', + 'go_to_website' => 'Go to {podcastWebsite}\'s website and unlock the podcast with your token.', + 'welcome_subject' => 'Welcome to {podcastTitle}', + 'welcome' => 'You have subscribed to {podcastTitle}, thank you and welcome aboard!', + 'welcome_token_title' => 'Here are your credentials to unlock the podcast\'s premium episodes:', + 'welcome_expires' => 'Your subscription was set to expire on {0}.', + 'welcome_never_expires' => 'Your subscription was set to never expire.', + 'reset_subject' => 'Your token was reset!', + 'reset_token' => 'Your access to {podcastTitle} has been reset!', + 'reset_token_title' => 'New credentials have been generated for you to unlock the podcast\'s premium episodes:', + 'edited_subject' => 'Your subscription has been updated!', + 'edited_expires' => 'Your subscription for {podcastTitle} was set to expire on {expiresAt}.', + 'edited_never_expires' => 'Your subscription for {podcastTitle} was set to never expire!', + 'suspended_subject' => 'Your subscription has been suspended!', + 'suspended' => 'Your subscription for {podcastTitle} has been suspended! You can no longer access the podcast\'s premium episodes.', + 'suspended_reason' => 'That is for the following reason: {0}', + 'resumed_subject' => 'Your subscription has been resumed!', + 'resumed' => 'Your subscription for {podcastTitle} has been resumed! You may access the podcast\'s premium episodes again.', + 'deleted_subject' => 'Your subscription has been removed!', + 'deleted' => 'Your subscription for {podcastTitle} has been removed! You no longer have access to the podcast\'s premium episodes.', + 'footer' => '{castopod} hosted on {host}', + ], +]; diff --git a/modules/PremiumPodcasts/Language/fr/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/fr/PremiumPodcasts.php index 18c0dd4e..007b90bd 100644 --- a/modules/PremiumPodcasts/Language/fr/PremiumPodcasts.php +++ b/modules/PremiumPodcasts/Language/fr/PremiumPodcasts.php @@ -9,26 +9,26 @@ declare(strict_types=1); */ return [ - 'podcast_is_premium' => 'Podcast contains premium episodes', - 'episode_is_premium' => 'Episode is premium, only available to premium subscribers', - 'unlock_episode' => 'This episode is for premium subscribers only. Click to unlock it!', - 'banner_unlock' => 'This podcast contains premium episodes, only available to premium subscribers.', - 'banner_lock' => 'Podcast is unlocked, enjoy the premium episodes!', - 'subscribe' => 'Subscribe', - 'lock' => 'Lock', - 'unlock' => 'Unlock', + 'podcast_is_premium' => 'Le Podcast contient des épisodes premium', + 'episode_is_premium' => 'Cet épisode est premium, uniquement disponible pour les abonnés premium', + 'unlock_episode' => 'Cet épisode est réservé aux abonnés premium. Cliquez pour le débloquer !', + 'banner_unlock' => 'Ce podcast contient des épisodes premium, uniquement disponibles pour les abonnés premium.', + 'banner_lock' => 'Podcast débloqué avec succès ! Profitez des épisodes premium !', + 'subscribe' => 'S\'abonner', + 'lock' => 'Verrouiller', + 'unlock' => 'Déverrouiller', 'unlock_form' => [ - 'title' => 'Premium content', - 'subtitle' => 'This podcast contains locked premium episodes! Do you have the key to unlock them?', - 'token' => 'Enter your key', - 'token_hint' => 'If you are subscribed to {podcastTitle}, you may copy the key that was sent to you via email and paste it here.', - 'submit' => 'Unlock all episodes!', - 'call_to_action' => 'Unlock all episodes of {podcastTitle}:', - 'subscribe_cta' => 'Subscribe now!', + 'title' => 'Contenu Premium', + 'subtitle' => 'Ce podcast contient des épisodes premium verrouillés ! Avez-vous la clé pour les déverrouiller ?', + 'token' => 'Entrez votre clé', + 'token_hint' => 'Si vous êtes abonné à {podcastTitle}, vous pouvez copier la clé qui vous a été envoyée par e-mail et la coller ici.', + 'submit' => 'Débloquer tous les épisodes!', + 'call_to_action' => 'Débloquer tous les épisodes de {podcastTitle}:', + 'subscribe_cta' => 'Je m’inscris maintenant !', ], 'messages' => [ - 'unlockSuccess' => 'Podcast was successfully unlocked! Enjoy the premium episodes!', - 'unlockBadAttempt' => 'Your key does not seem to be working…', - 'lockSuccess' => 'Podcast was successfully locked!', + 'unlockSuccess' => 'Podcast débloqué avec succès ! Profitez des épisodes premium !', + 'unlockBadAttempt' => 'Votre clé ne semble pas fonctionner…', + 'lockSuccess' => 'Le podcast a été verouillé avec succès !', ], ]; diff --git a/modules/PremiumPodcasts/Language/fr/Subscription.php b/modules/PremiumPodcasts/Language/fr/Subscription.php index f8af256f..f760c1ae 100644 --- a/modules/PremiumPodcasts/Language/fr/Subscription.php +++ b/modules/PremiumPodcasts/Language/fr/Subscription.php @@ -9,92 +9,92 @@ declare(strict_types=1); */ return [ - 'podcast_subscriptions' => 'Podcast subscriptions', - 'add' => 'New subscription', - 'view' => 'View subscription', - 'edit' => 'Edit subscription', - 'regenerate_token' => 'Regenerate token', - 'suspend' => 'Suspend subscription', - 'resume' => 'Resume subscription', - 'delete' => 'Delete subscription', + 'podcast_subscriptions' => 'Abonnements au podcast', + 'add' => 'Nouvel abonnement', + 'view' => 'Afficher l\'abonnement', + 'edit' => 'Modifier l\'inscription', + 'regenerate_token' => 'Regenerer le token', + 'suspend' => 'Suspendre l\'abonnement', + 'resume' => 'Reprendre l\'abonnement', + 'delete' => 'Supprimer l\'abonnement', 'status' => [ - 'active' => 'Active', - 'suspended' => 'Suspended', - 'expired' => 'Expired', + 'active' => 'Actif', + 'suspended' => 'Suspendu', + 'expired' => 'Expiré', ], 'list' => [ - 'number' => 'Number', - 'email' => 'Email', - 'expiration_date' => 'Expiration date', - 'unlimited' => 'Unlimited', - 'downloads' => 'Downloads', - 'status' => 'Status', + 'number' => 'Numéro', + 'email' => 'Adresse e-mail', + 'expiration_date' => 'Date d\'expiration', + 'unlimited' => 'Illimité', + 'downloads' => 'Téléchargements', + 'status' => 'Statut', ], 'form' => [ - 'email' => 'Email', - 'expiration_date' => 'Expiration date', - 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', - 'submit_edit' => 'Edit subscription', + 'email' => 'Adresse e-mail', + 'expiration_date' => 'Date d\'expiration', + 'expiration_date_hint' => 'La date et l\'heure à laquelle l\'abonnement expire. Laissez vide pour un abonnement illimité.', + 'submit_create' => 'Créer un abonnement', + 'submit_edit' => 'Modifier l\'inscription', ], 'form_link_add' => [ - 'link' => 'Subscription page link', - 'link_hint' => 'This will add a call to action in the website inviting listeners to subscribe to the podcast.', - 'submit' => 'Save link', + 'link' => 'Lien vers la page d\'abonnement', + 'link_hint' => 'Cela va ajouter un appel à l\'action dans le site Web invitant les auditeurs à s\'abonner au podcast.', + 'submit' => 'Enregistrer le lien', ], 'suspend_form' => [ - 'disclaimer' => 'Suspending the subscription will restrict the subscriber from having access to the premium content. You will still be able to lift the suspension afterwards.', - 'reason' => 'Reason', - 'reason_placeholder' => 'Why are you suspending the subscription?', - "submit" => 'Suspend subscription', + 'disclaimer' => 'Suspendre l\'abonnement empêchera l\'abonné d\'avoir accès au contenu premium. Vous pourrez toujours lever la suspension par la suite.', + 'reason' => 'Raison', + 'reason_placeholder' => 'Pour quelle raison arrêtez vous votre abonnement ?', + "submit" => 'Suspendre l\'abonnement', ], 'delete_form' => [ - 'disclaimer' => 'Deleting {subscriber}\'s subscription will remove all analytics data associated with it.', - 'understand' => 'I understand, remove the subscription permanently', - 'submit' => 'Remove subscription', + 'disclaimer' => 'La suppression de l\'abonnement de {subscriber} supprimera toutes les données d\'analyse qui lui sont associées.', + 'understand' => 'Je comprends, supprimez l\'abonnement définitivement', + 'submit' => 'Supprimer l\'abonnement', ], 'messages' => [ - 'addSuccess' => 'New subscription added! A welcome email was sent to {subscriber}.', - 'addError' => 'Subscription could not be added.', - 'editSuccess' => 'Subscription expiry date was updated! An email was sent to {subscriber}.', - 'editError' => 'Subscription could not be edited.', - 'regenerateTokenSuccess' => 'Token regenerated! An email was sent to {subscriber} with the new token.', - 'regenerateTokenError' => 'Token could not be regenerated.', - 'deleteSuccess' => 'Subscription was removed! An email was sent to {subscriber}.', - 'deleteError' => 'Subscription could not be removed.', - 'suspendSuccess' => 'Subscription was suspended! An email was sent to {subscriber}.', - 'suspendError' => 'Subscription could not be suspended.', - 'resumeSuccess' => 'Subscription was resumed! An email was sent to {subscriber}.', - 'resumeError' => 'Subscription could not be resumed.', - 'linkSaveSuccess' => 'Subscription link was saved successfully! It will appear in the website as a Call To Action!', - 'linkRemoveSuccess' => 'Subscription link was removed successfully!', + 'addSuccess' => 'Un nouvel abonnement a été ajouté ! Un e-mail de bienvenue a été envoyé à {subscriber}.', + 'addError' => 'L\'abonnement n\'a pu être ajouté.', + 'editSuccess' => 'La date d\'expiration de l\'abonnement a été mise à jour ! Un e-mail a été envoyé à {subscriber}.', + 'editError' => 'L\'abonnement n\'a pas pu être modifié.', + 'regenerateTokenSuccess' => 'Jeton régénéré ! Un email a été envoyé à {subscriber} avec le nouveau jeton.', + 'regenerateTokenError' => 'Le jeton n\'a pas pu être régénéré.', + 'deleteSuccess' => 'L\'abonnement a été suspendu! Un e-mail a été envoyé à {subscriber}.', + 'deleteError' => 'L\'abonnement n\'a pas pu être supprimé.', + 'suspendSuccess' => 'L\'abonnement a été suspendu! Un e-mail a été envoyé à {subscriber}.', + 'suspendError' => 'L\'abonnement ne peut pas être suspendu.', + 'resumeSuccess' => 'L\'abonnement a été suspendu! Un e-mail a été envoyé à {subscriber}.', + 'resumeError' => 'L\'abonnement n\'a pas pu être repris.', + 'linkSaveSuccess' => 'Le lien de l\'abonnement a été enregistré avec succès ! Il apparaîtra sur le site comme un Appel à l\'action !', + 'linkRemoveSuccess' => 'Le lien de l\'abonnement a été supprimé avec succès !', ], 'emails' => [ - 'greeting' => 'Hey,', - 'token' => 'Your token: {0}', - 'unique_feed_link' => 'Your unique feed link: {0}', - 'how_to_use' => 'How to use?', - 'two_ways' => 'You have two ways of unlocking the premium episodes:', - 'import_into_app' => 'Copy your unique feed url inside your favourite podcast app (import it as a private feed to prevent exposing your credentials).', - 'go_to_website' => 'Go to {podcastWebsite}\'s website and unlock the podcast with your token.', - 'welcome_subject' => 'Welcome to {podcastTitle}', - 'welcome' => 'You have subscribed to {podcastTitle}, thank you and welcome aboard!', - 'welcome_token_title' => 'Here are your credentials to unlock the podcast\'s premium episodes:', - 'welcome_expires' => 'Your subscription was set to expire on {0}.', - 'welcome_never_expires' => 'Your subscription was set to never expire.', - 'reset_subject' => 'Your token was reset!', - 'reset_token' => 'Your access to {podcastTitle} has been reset!', - 'reset_token_title' => 'New credentials have been generated for you to unlock the podcast\'s premium episodes:', - 'edited_subject' => 'Your subscription has been updated!', - 'edited_expires' => 'Your subscription for {podcastTitle} was set to expire on {expiresAt}.', - 'edited_never_expires' => 'Your subscription for {podcastTitle} was set to never expire!', - 'suspended_subject' => 'Your subscription has been suspended!', - 'suspended' => 'Your subscription for {podcastTitle} has been suspended! You can no longer access the podcast\'s premium episodes.', - 'suspended_reason' => 'That is for the following reason: {0}', - 'resumed_subject' => 'Your subscription has been resumed!', - 'resumed' => 'Your subscription for {podcastTitle} has been resumed! You may access the podcast\'s premium episodes again.', - 'deleted_subject' => 'Your subscription has been removed!', - 'deleted' => 'Your subscription for {podcastTitle} has been removed! You no longer have access to the podcast\'s premium episodes.', - 'footer' => '{castopod} hosted on {host}', + 'greeting' => 'Hé,', + 'token' => 'Votre jeton : {0}', + 'unique_feed_link' => 'Votre lien de flux unique : {0}', + 'how_to_use' => 'Comment l\'utiliser ?', + 'two_ways' => 'Vous avez deux façons de débloquer les épisodes premium :', + 'import_into_app' => 'Copiez votre URL de flux unique dans votre application de baladodiffusion préférée (importez-la en tant que flux privé pour éviter de dévoiler vos identifiants).', + 'go_to_website' => 'Rendez-vous sur le site web de {podcastWebsite} et débloquez le podcast avec votre jeton.', + 'welcome_subject' => 'Bienvenue sur {podcastTitle}', + 'welcome' => 'Vous vous êtes abonné à {podcastTitle}, merci et bienvenue à bord !', + 'welcome_token_title' => 'Voici vos identifiants pour débloquer les épisodes premium du podcast:', + 'welcome_expires' => 'Votre abonnement a été configuré pour expirer le {0}.', + 'welcome_never_expires' => 'Votre abonnement a été configuré pour ne jamais expirer.', + 'reset_subject' => 'Votre jeton a été réinitialisé !', + 'reset_token' => 'Votre accès à {podcastTitle} a été réinitialisé !', + 'reset_token_title' => 'De nouveaux identifiants ont été générés pour vous permettre de déverrouiller les épisodes premium du podcast:', + 'edited_subject' => 'Votre abonnement a été mis à jour !', + 'edited_expires' => 'Votre abonnement pour {podcastTitle} a été configuré pour expirer le {expiresAt}.', + 'edited_never_expires' => 'Votre abonnement pour {podcastTitle} a été configuré pour ne jamais expirer !', + 'suspended_subject' => 'Votre abonnement a été suspendu !', + 'suspended' => 'Votre abonnement à {podcastTitle} a été suspendu ! Vous ne pouvez plus accéder aux épisodes premium du podcast.', + 'suspended_reason' => 'Pour la raison suivante : {0}', + 'resumed_subject' => 'Votre abonnement a été réactivé !', + 'resumed' => 'Votre abonnement à {podcastTitle} a été réactivé ! Vous pouvez à nouveau accéder aux épisodes premium du podcast.', + 'deleted_subject' => 'Votre abonnement a été supprimé !', + 'deleted' => 'Votre abonnement pour {podcastTitle} a été supprimé ! Vous n\'avez plus accès aux épisodes premium du podcast.', + 'footer' => '{castopod} hébergé sur {host}', ], ]; diff --git a/modules/PremiumPodcasts/Language/fr2/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/fr2/PremiumPodcasts.php new file mode 100644 index 00000000..18c0dd4e --- /dev/null +++ b/modules/PremiumPodcasts/Language/fr2/PremiumPodcasts.php @@ -0,0 +1,34 @@ + 'Podcast contains premium episodes', + 'episode_is_premium' => 'Episode is premium, only available to premium subscribers', + 'unlock_episode' => 'This episode is for premium subscribers only. Click to unlock it!', + 'banner_unlock' => 'This podcast contains premium episodes, only available to premium subscribers.', + 'banner_lock' => 'Podcast is unlocked, enjoy the premium episodes!', + 'subscribe' => 'Subscribe', + 'lock' => 'Lock', + 'unlock' => 'Unlock', + 'unlock_form' => [ + 'title' => 'Premium content', + 'subtitle' => 'This podcast contains locked premium episodes! Do you have the key to unlock them?', + 'token' => 'Enter your key', + 'token_hint' => 'If you are subscribed to {podcastTitle}, you may copy the key that was sent to you via email and paste it here.', + 'submit' => 'Unlock all episodes!', + 'call_to_action' => 'Unlock all episodes of {podcastTitle}:', + 'subscribe_cta' => 'Subscribe now!', + ], + 'messages' => [ + 'unlockSuccess' => 'Podcast was successfully unlocked! Enjoy the premium episodes!', + 'unlockBadAttempt' => 'Your key does not seem to be working…', + 'lockSuccess' => 'Podcast was successfully locked!', + ], +]; diff --git a/modules/PremiumPodcasts/Language/fr2/Subscription.php b/modules/PremiumPodcasts/Language/fr2/Subscription.php new file mode 100644 index 00000000..e83f0cb2 --- /dev/null +++ b/modules/PremiumPodcasts/Language/fr2/Subscription.php @@ -0,0 +1,100 @@ + 'Podcast subscriptions', + 'add' => 'New subscription', + 'view' => 'View subscription', + 'edit' => 'Edit subscription', + 'regenerate_token' => 'Regenerate token', + 'suspend' => 'Suspend subscription', + 'resume' => 'Resume subscription', + 'delete' => 'Delete subscription', + 'status' => [ + 'active' => 'Active', + 'suspended' => 'Suspended', + 'expired' => 'Expired', + ], + 'list' => [ + 'number' => 'Number', + 'email' => 'Email', + 'expiration_date' => 'Expiration date', + 'unlimited' => 'Unlimited', + 'downloads' => 'Downloads', + 'status' => 'Status', + ], + 'form' => [ + 'email' => 'Email', + 'expiration_date' => 'Expiration date', + 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', + 'submit_create' => 'Create subscription', + 'submit_edit' => 'Edit subscription', + ], + 'form_link_add' => [ + 'link' => 'Subscription page link', + 'link_hint' => 'This will add a call to action in the website inviting listeners to subscribe to the podcast.', + 'submit' => 'Save link', + ], + 'suspend_form' => [ + 'disclaimer' => 'Suspending the subscription will restrict the subscriber from having access to the premium content. You will still be able to lift the suspension afterwards.', + 'reason' => 'Reason', + 'reason_placeholder' => 'Why are you suspending the subscription?', + "submit" => 'Suspend subscription', + ], + 'delete_form' => [ + 'disclaimer' => 'Deleting {subscriber}\'s subscription will remove all analytics data associated with it.', + 'understand' => 'I understand, remove the subscription permanently', + 'submit' => 'Remove subscription', + ], + 'messages' => [ + 'addSuccess' => 'New subscription added! A welcome email was sent to {subscriber}.', + 'addError' => 'Subscription could not be added.', + 'editSuccess' => 'Subscription expiry date was updated! An email was sent to {subscriber}.', + 'editError' => 'Subscription could not be edited.', + 'regenerateTokenSuccess' => 'Token regenerated! An email was sent to {subscriber} with the new token.', + 'regenerateTokenError' => 'Token could not be regenerated.', + 'deleteSuccess' => 'Subscription was removed! An email was sent to {subscriber}.', + 'deleteError' => 'Subscription could not be removed.', + 'suspendSuccess' => 'Subscription was suspended! An email was sent to {subscriber}.', + 'suspendError' => 'Subscription could not be suspended.', + 'resumeSuccess' => 'Subscription was resumed! An email was sent to {subscriber}.', + 'resumeError' => 'Subscription could not be resumed.', + 'linkSaveSuccess' => 'Subscription link was saved successfully! It will appear in the website as a Call To Action!', + 'linkRemoveSuccess' => 'Subscription link was removed successfully!', + ], + 'emails' => [ + 'greeting' => 'Hey,', + 'token' => 'Your token: {0}', + 'unique_feed_link' => 'Your unique feed link: {0}', + 'how_to_use' => 'How to use?', + 'two_ways' => 'You have two ways of unlocking the premium episodes:', + 'import_into_app' => 'Copy your unique feed url inside your favourite podcast app (import it as a private feed to prevent exposing your credentials).', + 'go_to_website' => 'Go to {podcastWebsite}\'s website and unlock the podcast with your token.', + 'welcome_subject' => 'Welcome to {podcastTitle}', + 'welcome' => 'You have subscribed to {podcastTitle}, thank you and welcome aboard!', + 'welcome_token_title' => 'Here are your credentials to unlock the podcast\'s premium episodes:', + 'welcome_expires' => 'Your subscription was set to expire on {0}.', + 'welcome_never_expires' => 'Your subscription was set to never expire.', + 'reset_subject' => 'Your token was reset!', + 'reset_token' => 'Your access to {podcastTitle} has been reset!', + 'reset_token_title' => 'New credentials have been generated for you to unlock the podcast\'s premium episodes:', + 'edited_subject' => 'Your subscription has been updated!', + 'edited_expires' => 'Your subscription for {podcastTitle} was set to expire on {expiresAt}.', + 'edited_never_expires' => 'Your subscription for {podcastTitle} was set to never expire!', + 'suspended_subject' => 'Your subscription has been suspended!', + 'suspended' => 'Your subscription for {podcastTitle} has been suspended! You can no longer access the podcast\'s premium episodes.', + 'suspended_reason' => 'That is for the following reason: {0}', + 'resumed_subject' => 'Your subscription has been resumed!', + 'resumed' => 'Your subscription for {podcastTitle} has been resumed! You may access the podcast\'s premium episodes again.', + 'deleted_subject' => 'Your subscription has been removed!', + 'deleted' => 'Your subscription for {podcastTitle} has been removed! You no longer have access to the podcast\'s premium episodes.', + 'footer' => '{castopod} hosted on {host}', + ], +]; diff --git a/modules/PremiumPodcasts/Language/gd/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/gd/PremiumPodcasts.php index 18c0dd4e..ac6d33e4 100644 --- a/modules/PremiumPodcasts/Language/gd/PremiumPodcasts.php +++ b/modules/PremiumPodcasts/Language/gd/PremiumPodcasts.php @@ -9,26 +9,26 @@ declare(strict_types=1); */ return [ - 'podcast_is_premium' => 'Podcast contains premium episodes', - 'episode_is_premium' => 'Episode is premium, only available to premium subscribers', - 'unlock_episode' => 'This episode is for premium subscribers only. Click to unlock it!', - 'banner_unlock' => 'This podcast contains premium episodes, only available to premium subscribers.', - 'banner_lock' => 'Podcast is unlocked, enjoy the premium episodes!', - 'subscribe' => 'Subscribe', - 'lock' => 'Lock', - 'unlock' => 'Unlock', + 'podcast_is_premium' => 'Tha eapasodan premium aig a’ phod-chraoladh seo', + 'episode_is_premium' => 'Seo eapasod premium, chan eil e ri fhaighinn ach do dh’fho-sgrìobhaichean premium', + 'unlock_episode' => 'Tha an t-eapasod seo do dh’fho-sgrìobhaichean premium a-mhàin. Briog air gus a’ ghlas a thoirt fo bhàrr!', + 'banner_unlock' => 'Tha eapasodan premium sa phod-chraoladh seo nach eil ri fhaighinn ach do dh’fho-sgrìobhaichean premium.', + 'banner_lock' => 'Thug thu a’ ghlas far a’ phod-chraolaidh, gabh tlachd às na h-eapasodan premium!', + 'subscribe' => 'Fo-sgrìobh', + 'lock' => 'Glais', + 'unlock' => 'Thoir a’ ghlas dheth', 'unlock_form' => [ - 'title' => 'Premium content', - 'subtitle' => 'This podcast contains locked premium episodes! Do you have the key to unlock them?', - 'token' => 'Enter your key', - 'token_hint' => 'If you are subscribed to {podcastTitle}, you may copy the key that was sent to you via email and paste it here.', - 'submit' => 'Unlock all episodes!', - 'call_to_action' => 'Unlock all episodes of {podcastTitle}:', - 'subscribe_cta' => 'Subscribe now!', + 'title' => 'Susbaint premium', + 'subtitle' => 'Tha eapasodan premium glaiste aig a’ phod-chraoladh seo! A bheil iuchair agad gus a’ ghlas a thoirt fo am bàrr?', + 'token' => 'Cuir a-steach an iuchair agad', + 'token_hint' => 'Ma fhuair thu fo-sgrìobhadh air {podcastTitle}, ’s urrainn dhut lethbhreac a dhèanamh dhen iuchair a chaidh a chur thugad air a’ phost-d ’s a cur ann an-seo.', + 'submit' => 'Thoir a’ ghlas far a h-uile eapasod!', + 'call_to_action' => 'Thoir a’ ghlas far a h-uile eapasod aig {podcastTitle}:', + 'subscribe_cta' => 'Fo-sgrìobh an-dràsta!', ], 'messages' => [ - 'unlockSuccess' => 'Podcast was successfully unlocked! Enjoy the premium episodes!', - 'unlockBadAttempt' => 'Your key does not seem to be working…', - 'lockSuccess' => 'Podcast was successfully locked!', + 'unlockSuccess' => 'Thug thu a’ ghlas far a’ phod-chraolaidh! Gabh tlachd às na h-eapasodan premium!', + 'unlockBadAttempt' => 'Tha coltas nach eil an iuchair agad ag obair…', + 'lockSuccess' => 'Chaidh am pod-chraoladh a ghlasadh!', ], ]; diff --git a/modules/PremiumPodcasts/Language/gd/Subscription.php b/modules/PremiumPodcasts/Language/gd/Subscription.php index f8af256f..2dbfdeb6 100644 --- a/modules/PremiumPodcasts/Language/gd/Subscription.php +++ b/modules/PremiumPodcasts/Language/gd/Subscription.php @@ -9,92 +9,92 @@ declare(strict_types=1); */ return [ - 'podcast_subscriptions' => 'Podcast subscriptions', - 'add' => 'New subscription', - 'view' => 'View subscription', - 'edit' => 'Edit subscription', - 'regenerate_token' => 'Regenerate token', - 'suspend' => 'Suspend subscription', - 'resume' => 'Resume subscription', - 'delete' => 'Delete subscription', + 'podcast_subscriptions' => 'Fo-sgrìobhaidhean air pod-chraolaidhean', + 'add' => 'Fo-sgrìobhadh ùr', + 'view' => 'Seall am fo-sgrìobhadh', + 'edit' => 'Deasaich am fo-sgrìobhadh', + 'regenerate_token' => 'Ath-ghin an tòcan', + 'suspend' => 'Cuir am fo-sgrìobhadh à rèim', + 'resume' => 'Lean air an fho-sgrìobhadh', + 'delete' => 'Sguab às am fo-sgrìobhadh', 'status' => [ - 'active' => 'Active', - 'suspended' => 'Suspended', - 'expired' => 'Expired', + 'active' => 'Gnìomhach', + 'suspended' => 'À rèim', + 'expired' => 'Dh’fhalbh an ùine air', ], 'list' => [ - 'number' => 'Number', - 'email' => 'Email', - 'expiration_date' => 'Expiration date', - 'unlimited' => 'Unlimited', - 'downloads' => 'Downloads', - 'status' => 'Status', + 'number' => 'Àireamh', + 'email' => 'Post-d', + 'expiration_date' => 'Ceann-là crìochnachaidh', + 'unlimited' => 'Gun chrìoch', + 'downloads' => 'Luchdaidhean a-nuas', + 'status' => 'Staid', ], 'form' => [ - 'email' => 'Email', - 'expiration_date' => 'Expiration date', - 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', - 'submit_edit' => 'Edit subscription', + 'email' => 'Post-d', + 'expiration_date' => 'Ceann-là crìochnachaidh', + 'expiration_date_hint' => 'An ceann-là ’s àm a dh’fhalbhas an ùine air an fho-sgrìobhadh. Fàg bàn e airson fo-sgrìobhadh gun chrìoch.', + 'submit_create' => 'Create subscription', + 'submit_edit' => 'Deasaich am fo-sgrìobhadh', ], 'form_link_add' => [ - 'link' => 'Subscription page link', - 'link_hint' => 'This will add a call to action in the website inviting listeners to subscribe to the podcast.', - 'submit' => 'Save link', + 'link' => 'Ceangal gu duilleag an fho-sgrìobhaidh', + 'link_hint' => 'Cuiridh seo tàladh ris an làrach-lìn a bheir cuireadh dhan luchd-èisteachd ach am faigh iad fo-sgrìobhadh air a’ phod-chraoladh.', + 'submit' => 'Sàbhail an ceangal', ], 'suspend_form' => [ - 'disclaimer' => 'Suspending the subscription will restrict the subscriber from having access to the premium content. You will still be able to lift the suspension afterwards.', - 'reason' => 'Reason', - 'reason_placeholder' => 'Why are you suspending the subscription?', - "submit" => 'Suspend subscription', + 'disclaimer' => 'Ma chuireas tu am fo-sgrìobhadh à rèim, cuingichidh seo an neach fo-sgrìobhaidh gus nach fhaigh iad cothrom air susbaint premium. ’S urrainn dhut a chur ann an rèim a-rithist uair sam bith an uairsin.', + 'reason' => 'Adhbhar', + 'reason_placeholder' => 'Carson a tha thu a’ cur am fo-sgrìobhadh à rèim?', + "submit" => 'Cuir am fo-sgrìobhadh à rèim', ], 'delete_form' => [ - 'disclaimer' => 'Deleting {subscriber}\'s subscription will remove all analytics data associated with it.', - 'understand' => 'I understand, remove the subscription permanently', - 'submit' => 'Remove subscription', + 'disclaimer' => 'Ma sguabas tu às am fo-sgrìobhadh aig {subscriber}, bheir seo air falbh dàta sam bith na h-anailiseachd a tha co-cheangailte ris.', + 'understand' => 'Tha mi agaibh, thoir air falbh am fo-sgrìobhadh gu buan', + 'submit' => 'Thoir am fo-sgrìobhadh air falbh', ], 'messages' => [ - 'addSuccess' => 'New subscription added! A welcome email was sent to {subscriber}.', - 'addError' => 'Subscription could not be added.', - 'editSuccess' => 'Subscription expiry date was updated! An email was sent to {subscriber}.', - 'editError' => 'Subscription could not be edited.', - 'regenerateTokenSuccess' => 'Token regenerated! An email was sent to {subscriber} with the new token.', - 'regenerateTokenError' => 'Token could not be regenerated.', - 'deleteSuccess' => 'Subscription was removed! An email was sent to {subscriber}.', - 'deleteError' => 'Subscription could not be removed.', - 'suspendSuccess' => 'Subscription was suspended! An email was sent to {subscriber}.', - 'suspendError' => 'Subscription could not be suspended.', - 'resumeSuccess' => 'Subscription was resumed! An email was sent to {subscriber}.', - 'resumeError' => 'Subscription could not be resumed.', - 'linkSaveSuccess' => 'Subscription link was saved successfully! It will appear in the website as a Call To Action!', - 'linkRemoveSuccess' => 'Subscription link was removed successfully!', + 'addSuccess' => 'Chaidh fo-sgrìobhadh ùr a chur ris! Chaidh post-d fàilteachaidh a chur gu {subscriber}.', + 'addError' => 'Cha b’ urrainn dhuinn am fo-sgrìobhadh a chur ris.', + 'editSuccess' => 'Chaidh an ceann-là a dh’fhalbhas an ùine air an fho-sgrìobhadh ùrachadh! Chaidh post-d a chur gu {subscriber}.', + 'editError' => 'Cha b’ urrainn dhuinn am fo-sgrìobhadh a dheasachadh.', + 'regenerateTokenSuccess' => 'Chaidh an tòcan ath-ghintinn! Chaidh post-d a chur gu {subscriber} leis an tòcan ùr.', + 'regenerateTokenError' => 'Cha b’ urrainn dhuinn an tòcan ath-ghintinn.', + 'deleteSuccess' => 'Chaidh am fo-sgrìobhadh a thoirt air falbh! Chaidh post-d a chur gu {subscriber}.', + 'deleteError' => 'Cha b’ urrainn dhuinn am fo-sgrìobhadh a thoirt air falbh.', + 'suspendSuccess' => 'Chaidh am fo-sgrìobhadh a chur à rèim! Chaidh post-d a chur gu {subscriber}.', + 'suspendError' => 'Cha b’ urrainn dhuinn am fo-sgrìobhadh a cur à rèim.', + 'resumeSuccess' => 'Chaidh leantainn air an fho-sgrìobhadh! Chaidh post-d a chur gu {subscriber}.', + 'resumeError' => 'Cha b’ urrainn dhuinn leantainn air an fho-sgrìobhadh.', + 'linkSaveSuccess' => 'Chaidh an ceangal dhan fho-sgrìobhadh a shàbhaladh! Nochdaidh e air an làrach-lìn na thadhladh!', + 'linkRemoveSuccess' => 'Chaidh an ceangal dhan fho-sgrìobhadh a thoirt air falbh!', ], 'emails' => [ - 'greeting' => 'Hey,', - 'token' => 'Your token: {0}', - 'unique_feed_link' => 'Your unique feed link: {0}', - 'how_to_use' => 'How to use?', - 'two_ways' => 'You have two ways of unlocking the premium episodes:', - 'import_into_app' => 'Copy your unique feed url inside your favourite podcast app (import it as a private feed to prevent exposing your credentials).', - 'go_to_website' => 'Go to {podcastWebsite}\'s website and unlock the podcast with your token.', - 'welcome_subject' => 'Welcome to {podcastTitle}', - 'welcome' => 'You have subscribed to {podcastTitle}, thank you and welcome aboard!', - 'welcome_token_title' => 'Here are your credentials to unlock the podcast\'s premium episodes:', - 'welcome_expires' => 'Your subscription was set to expire on {0}.', - 'welcome_never_expires' => 'Your subscription was set to never expire.', - 'reset_subject' => 'Your token was reset!', - 'reset_token' => 'Your access to {podcastTitle} has been reset!', - 'reset_token_title' => 'New credentials have been generated for you to unlock the podcast\'s premium episodes:', - 'edited_subject' => 'Your subscription has been updated!', - 'edited_expires' => 'Your subscription for {podcastTitle} was set to expire on {expiresAt}.', - 'edited_never_expires' => 'Your subscription for {podcastTitle} was set to never expire!', - 'suspended_subject' => 'Your subscription has been suspended!', - 'suspended' => 'Your subscription for {podcastTitle} has been suspended! You can no longer access the podcast\'s premium episodes.', - 'suspended_reason' => 'That is for the following reason: {0}', - 'resumed_subject' => 'Your subscription has been resumed!', - 'resumed' => 'Your subscription for {podcastTitle} has been resumed! You may access the podcast\'s premium episodes again.', - 'deleted_subject' => 'Your subscription has been removed!', - 'deleted' => 'Your subscription for {podcastTitle} has been removed! You no longer have access to the podcast\'s premium episodes.', - 'footer' => '{castopod} hosted on {host}', + 'greeting' => 'Shin thu,', + 'token' => 'Seo an tòcan agad: {0}', + 'unique_feed_link' => 'Seo ceangal àraidh an inbhir agad: {0}', + 'how_to_use' => 'Mar a chleachdas tu e', + 'two_ways' => 'Tha dà dhòigh ann air an doir thu a’ ghlas far eapasodan premium:', + 'import_into_app' => 'Cuir lethbhreac de dh’URL àraidh an inbhir agad san aplacaid pod-chraolaidh as fheàrr leat (ion-phortaich e ’na inbhir prìobhaideach ach nach foillsich thu an teisteanas agad).', + 'go_to_website' => 'Tadhail air an làrach-lìn aig {podcastWebsite} agus thoir a’ ghlas far a’ phod-chraolaidh leis an tòcan agad.', + 'welcome_subject' => 'Fàilte gu {podcastTitle}', + 'welcome' => 'Fhuair thu fo-sgrìobhadh air {podcastTitle}, mòran taing is fàilte air bhòrd!', + 'welcome_token_title' => 'Seo an teisteanas agad airson a’ ghlas a thoirt far eapasodan premium a’ phod-chraolaidh:', + 'welcome_expires' => 'Chaidh crìoch an fho-sgrìobhaidh agad a shuidheachadh air {0}.', + 'welcome_never_expires' => 'Chaidh am fo-sgrìobhaidh agad a shuidheachadh ach nach fhalbh an ùine air.', + 'reset_subject' => 'Chaidh an tòcan agad ath-shuidheachadh!', + 'reset_token' => 'Chaidh an t-inntrigeadh agad air {podcastTitle} ath-shuidheachadh!', + 'reset_token_title' => 'Chaidh teisteanas ùr a ghintinn dhut airson a’ ghlas a thoirt far eapasodan premium a’ phod-chraolaidh:', + 'edited_subject' => 'Chaidh am fo-sgrìobhadh agad ùrachadh!', + 'edited_expires' => 'Chaidh crìoch an fho-sgrìobhaidh agad air {podcastTitle} a shuidheachadh air {expiresAt}.', + 'edited_never_expires' => 'Chaidh am fo-sgrìobhaidh agad air {podcastTitle} a shuidheachadh ach nach fhalbh an ùine air!', + 'suspended_subject' => 'Chaidh am fo-sgrìobhadh agad a chur à rèim!', + 'suspended' => 'Chaidh am fo-sgrìobhadh agad air {podcastTitle} a chur à rèim! Chan urrainn dhut eapasodan premium a’ phod-chraolaidh inntrigeadh tuilleadh.', + 'suspended_reason' => 'Seo as adhbhar dha: {0}', + 'resumed_subject' => 'Chaidh leantainn air an fho-sgrìobhadh agad!', + 'resumed' => 'Chaidh leantainn air an fho-sgrìobhadh air {podcastTitle} agad! ’S urrainn dhut eapasodan premium a’ phod-chraolaidh inntrigeadh a-rithist.', + 'deleted_subject' => 'Chaidh am fo-sgrìobhadh agad a thoirt air falbh!', + 'deleted' => 'Chaidh am fo-sgrìobhadh agad air {podcastTitle} a thoirt air falbh! Chan urrainn dhut eapasodan premium a’ phod-chraolaidh inntrigeadh tuilleadh.', + 'footer' => 'Seo {castopod} ’ga òstadh air {host}', ], ]; diff --git a/modules/PremiumPodcasts/Language/gl/Subscription.php b/modules/PremiumPodcasts/Language/gl/Subscription.php index f8af256f..e83f0cb2 100644 --- a/modules/PremiumPodcasts/Language/gl/Subscription.php +++ b/modules/PremiumPodcasts/Language/gl/Subscription.php @@ -34,7 +34,7 @@ return [ 'email' => 'Email', 'expiration_date' => 'Expiration date', 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', + 'submit_create' => 'Create subscription', 'submit_edit' => 'Edit subscription', ], 'form_link_add' => [ diff --git a/modules/PremiumPodcasts/Language/id/Subscription.php b/modules/PremiumPodcasts/Language/id/Subscription.php index f8af256f..e83f0cb2 100644 --- a/modules/PremiumPodcasts/Language/id/Subscription.php +++ b/modules/PremiumPodcasts/Language/id/Subscription.php @@ -34,7 +34,7 @@ return [ 'email' => 'Email', 'expiration_date' => 'Expiration date', 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', + 'submit_create' => 'Create subscription', 'submit_edit' => 'Edit subscription', ], 'form_link_add' => [ diff --git a/modules/PremiumPodcasts/Language/it/Subscription.php b/modules/PremiumPodcasts/Language/it/Subscription.php index f8af256f..e83f0cb2 100644 --- a/modules/PremiumPodcasts/Language/it/Subscription.php +++ b/modules/PremiumPodcasts/Language/it/Subscription.php @@ -34,7 +34,7 @@ return [ 'email' => 'Email', 'expiration_date' => 'Expiration date', 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', + 'submit_create' => 'Create subscription', 'submit_edit' => 'Edit subscription', ], 'form_link_add' => [ diff --git a/modules/PremiumPodcasts/Language/ja/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/ja/PremiumPodcasts.php new file mode 100644 index 00000000..18c0dd4e --- /dev/null +++ b/modules/PremiumPodcasts/Language/ja/PremiumPodcasts.php @@ -0,0 +1,34 @@ + 'Podcast contains premium episodes', + 'episode_is_premium' => 'Episode is premium, only available to premium subscribers', + 'unlock_episode' => 'This episode is for premium subscribers only. Click to unlock it!', + 'banner_unlock' => 'This podcast contains premium episodes, only available to premium subscribers.', + 'banner_lock' => 'Podcast is unlocked, enjoy the premium episodes!', + 'subscribe' => 'Subscribe', + 'lock' => 'Lock', + 'unlock' => 'Unlock', + 'unlock_form' => [ + 'title' => 'Premium content', + 'subtitle' => 'This podcast contains locked premium episodes! Do you have the key to unlock them?', + 'token' => 'Enter your key', + 'token_hint' => 'If you are subscribed to {podcastTitle}, you may copy the key that was sent to you via email and paste it here.', + 'submit' => 'Unlock all episodes!', + 'call_to_action' => 'Unlock all episodes of {podcastTitle}:', + 'subscribe_cta' => 'Subscribe now!', + ], + 'messages' => [ + 'unlockSuccess' => 'Podcast was successfully unlocked! Enjoy the premium episodes!', + 'unlockBadAttempt' => 'Your key does not seem to be working…', + 'lockSuccess' => 'Podcast was successfully locked!', + ], +]; diff --git a/modules/PremiumPodcasts/Language/ja/Subscription.php b/modules/PremiumPodcasts/Language/ja/Subscription.php new file mode 100644 index 00000000..e83f0cb2 --- /dev/null +++ b/modules/PremiumPodcasts/Language/ja/Subscription.php @@ -0,0 +1,100 @@ + 'Podcast subscriptions', + 'add' => 'New subscription', + 'view' => 'View subscription', + 'edit' => 'Edit subscription', + 'regenerate_token' => 'Regenerate token', + 'suspend' => 'Suspend subscription', + 'resume' => 'Resume subscription', + 'delete' => 'Delete subscription', + 'status' => [ + 'active' => 'Active', + 'suspended' => 'Suspended', + 'expired' => 'Expired', + ], + 'list' => [ + 'number' => 'Number', + 'email' => 'Email', + 'expiration_date' => 'Expiration date', + 'unlimited' => 'Unlimited', + 'downloads' => 'Downloads', + 'status' => 'Status', + ], + 'form' => [ + 'email' => 'Email', + 'expiration_date' => 'Expiration date', + 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', + 'submit_create' => 'Create subscription', + 'submit_edit' => 'Edit subscription', + ], + 'form_link_add' => [ + 'link' => 'Subscription page link', + 'link_hint' => 'This will add a call to action in the website inviting listeners to subscribe to the podcast.', + 'submit' => 'Save link', + ], + 'suspend_form' => [ + 'disclaimer' => 'Suspending the subscription will restrict the subscriber from having access to the premium content. You will still be able to lift the suspension afterwards.', + 'reason' => 'Reason', + 'reason_placeholder' => 'Why are you suspending the subscription?', + "submit" => 'Suspend subscription', + ], + 'delete_form' => [ + 'disclaimer' => 'Deleting {subscriber}\'s subscription will remove all analytics data associated with it.', + 'understand' => 'I understand, remove the subscription permanently', + 'submit' => 'Remove subscription', + ], + 'messages' => [ + 'addSuccess' => 'New subscription added! A welcome email was sent to {subscriber}.', + 'addError' => 'Subscription could not be added.', + 'editSuccess' => 'Subscription expiry date was updated! An email was sent to {subscriber}.', + 'editError' => 'Subscription could not be edited.', + 'regenerateTokenSuccess' => 'Token regenerated! An email was sent to {subscriber} with the new token.', + 'regenerateTokenError' => 'Token could not be regenerated.', + 'deleteSuccess' => 'Subscription was removed! An email was sent to {subscriber}.', + 'deleteError' => 'Subscription could not be removed.', + 'suspendSuccess' => 'Subscription was suspended! An email was sent to {subscriber}.', + 'suspendError' => 'Subscription could not be suspended.', + 'resumeSuccess' => 'Subscription was resumed! An email was sent to {subscriber}.', + 'resumeError' => 'Subscription could not be resumed.', + 'linkSaveSuccess' => 'Subscription link was saved successfully! It will appear in the website as a Call To Action!', + 'linkRemoveSuccess' => 'Subscription link was removed successfully!', + ], + 'emails' => [ + 'greeting' => 'Hey,', + 'token' => 'Your token: {0}', + 'unique_feed_link' => 'Your unique feed link: {0}', + 'how_to_use' => 'How to use?', + 'two_ways' => 'You have two ways of unlocking the premium episodes:', + 'import_into_app' => 'Copy your unique feed url inside your favourite podcast app (import it as a private feed to prevent exposing your credentials).', + 'go_to_website' => 'Go to {podcastWebsite}\'s website and unlock the podcast with your token.', + 'welcome_subject' => 'Welcome to {podcastTitle}', + 'welcome' => 'You have subscribed to {podcastTitle}, thank you and welcome aboard!', + 'welcome_token_title' => 'Here are your credentials to unlock the podcast\'s premium episodes:', + 'welcome_expires' => 'Your subscription was set to expire on {0}.', + 'welcome_never_expires' => 'Your subscription was set to never expire.', + 'reset_subject' => 'Your token was reset!', + 'reset_token' => 'Your access to {podcastTitle} has been reset!', + 'reset_token_title' => 'New credentials have been generated for you to unlock the podcast\'s premium episodes:', + 'edited_subject' => 'Your subscription has been updated!', + 'edited_expires' => 'Your subscription for {podcastTitle} was set to expire on {expiresAt}.', + 'edited_never_expires' => 'Your subscription for {podcastTitle} was set to never expire!', + 'suspended_subject' => 'Your subscription has been suspended!', + 'suspended' => 'Your subscription for {podcastTitle} has been suspended! You can no longer access the podcast\'s premium episodes.', + 'suspended_reason' => 'That is for the following reason: {0}', + 'resumed_subject' => 'Your subscription has been resumed!', + 'resumed' => 'Your subscription for {podcastTitle} has been resumed! You may access the podcast\'s premium episodes again.', + 'deleted_subject' => 'Your subscription has been removed!', + 'deleted' => 'Your subscription for {podcastTitle} has been removed! You no longer have access to the podcast\'s premium episodes.', + 'footer' => '{castopod} hosted on {host}', + ], +]; diff --git a/modules/PremiumPodcasts/Language/kk/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/kk/PremiumPodcasts.php new file mode 100644 index 00000000..18c0dd4e --- /dev/null +++ b/modules/PremiumPodcasts/Language/kk/PremiumPodcasts.php @@ -0,0 +1,34 @@ + 'Podcast contains premium episodes', + 'episode_is_premium' => 'Episode is premium, only available to premium subscribers', + 'unlock_episode' => 'This episode is for premium subscribers only. Click to unlock it!', + 'banner_unlock' => 'This podcast contains premium episodes, only available to premium subscribers.', + 'banner_lock' => 'Podcast is unlocked, enjoy the premium episodes!', + 'subscribe' => 'Subscribe', + 'lock' => 'Lock', + 'unlock' => 'Unlock', + 'unlock_form' => [ + 'title' => 'Premium content', + 'subtitle' => 'This podcast contains locked premium episodes! Do you have the key to unlock them?', + 'token' => 'Enter your key', + 'token_hint' => 'If you are subscribed to {podcastTitle}, you may copy the key that was sent to you via email and paste it here.', + 'submit' => 'Unlock all episodes!', + 'call_to_action' => 'Unlock all episodes of {podcastTitle}:', + 'subscribe_cta' => 'Subscribe now!', + ], + 'messages' => [ + 'unlockSuccess' => 'Podcast was successfully unlocked! Enjoy the premium episodes!', + 'unlockBadAttempt' => 'Your key does not seem to be working…', + 'lockSuccess' => 'Podcast was successfully locked!', + ], +]; diff --git a/modules/PremiumPodcasts/Language/kk/Subscription.php b/modules/PremiumPodcasts/Language/kk/Subscription.php new file mode 100644 index 00000000..e83f0cb2 --- /dev/null +++ b/modules/PremiumPodcasts/Language/kk/Subscription.php @@ -0,0 +1,100 @@ + 'Podcast subscriptions', + 'add' => 'New subscription', + 'view' => 'View subscription', + 'edit' => 'Edit subscription', + 'regenerate_token' => 'Regenerate token', + 'suspend' => 'Suspend subscription', + 'resume' => 'Resume subscription', + 'delete' => 'Delete subscription', + 'status' => [ + 'active' => 'Active', + 'suspended' => 'Suspended', + 'expired' => 'Expired', + ], + 'list' => [ + 'number' => 'Number', + 'email' => 'Email', + 'expiration_date' => 'Expiration date', + 'unlimited' => 'Unlimited', + 'downloads' => 'Downloads', + 'status' => 'Status', + ], + 'form' => [ + 'email' => 'Email', + 'expiration_date' => 'Expiration date', + 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', + 'submit_create' => 'Create subscription', + 'submit_edit' => 'Edit subscription', + ], + 'form_link_add' => [ + 'link' => 'Subscription page link', + 'link_hint' => 'This will add a call to action in the website inviting listeners to subscribe to the podcast.', + 'submit' => 'Save link', + ], + 'suspend_form' => [ + 'disclaimer' => 'Suspending the subscription will restrict the subscriber from having access to the premium content. You will still be able to lift the suspension afterwards.', + 'reason' => 'Reason', + 'reason_placeholder' => 'Why are you suspending the subscription?', + "submit" => 'Suspend subscription', + ], + 'delete_form' => [ + 'disclaimer' => 'Deleting {subscriber}\'s subscription will remove all analytics data associated with it.', + 'understand' => 'I understand, remove the subscription permanently', + 'submit' => 'Remove subscription', + ], + 'messages' => [ + 'addSuccess' => 'New subscription added! A welcome email was sent to {subscriber}.', + 'addError' => 'Subscription could not be added.', + 'editSuccess' => 'Subscription expiry date was updated! An email was sent to {subscriber}.', + 'editError' => 'Subscription could not be edited.', + 'regenerateTokenSuccess' => 'Token regenerated! An email was sent to {subscriber} with the new token.', + 'regenerateTokenError' => 'Token could not be regenerated.', + 'deleteSuccess' => 'Subscription was removed! An email was sent to {subscriber}.', + 'deleteError' => 'Subscription could not be removed.', + 'suspendSuccess' => 'Subscription was suspended! An email was sent to {subscriber}.', + 'suspendError' => 'Subscription could not be suspended.', + 'resumeSuccess' => 'Subscription was resumed! An email was sent to {subscriber}.', + 'resumeError' => 'Subscription could not be resumed.', + 'linkSaveSuccess' => 'Subscription link was saved successfully! It will appear in the website as a Call To Action!', + 'linkRemoveSuccess' => 'Subscription link was removed successfully!', + ], + 'emails' => [ + 'greeting' => 'Hey,', + 'token' => 'Your token: {0}', + 'unique_feed_link' => 'Your unique feed link: {0}', + 'how_to_use' => 'How to use?', + 'two_ways' => 'You have two ways of unlocking the premium episodes:', + 'import_into_app' => 'Copy your unique feed url inside your favourite podcast app (import it as a private feed to prevent exposing your credentials).', + 'go_to_website' => 'Go to {podcastWebsite}\'s website and unlock the podcast with your token.', + 'welcome_subject' => 'Welcome to {podcastTitle}', + 'welcome' => 'You have subscribed to {podcastTitle}, thank you and welcome aboard!', + 'welcome_token_title' => 'Here are your credentials to unlock the podcast\'s premium episodes:', + 'welcome_expires' => 'Your subscription was set to expire on {0}.', + 'welcome_never_expires' => 'Your subscription was set to never expire.', + 'reset_subject' => 'Your token was reset!', + 'reset_token' => 'Your access to {podcastTitle} has been reset!', + 'reset_token_title' => 'New credentials have been generated for you to unlock the podcast\'s premium episodes:', + 'edited_subject' => 'Your subscription has been updated!', + 'edited_expires' => 'Your subscription for {podcastTitle} was set to expire on {expiresAt}.', + 'edited_never_expires' => 'Your subscription for {podcastTitle} was set to never expire!', + 'suspended_subject' => 'Your subscription has been suspended!', + 'suspended' => 'Your subscription for {podcastTitle} has been suspended! You can no longer access the podcast\'s premium episodes.', + 'suspended_reason' => 'That is for the following reason: {0}', + 'resumed_subject' => 'Your subscription has been resumed!', + 'resumed' => 'Your subscription for {podcastTitle} has been resumed! You may access the podcast\'s premium episodes again.', + 'deleted_subject' => 'Your subscription has been removed!', + 'deleted' => 'Your subscription for {podcastTitle} has been removed! You no longer have access to the podcast\'s premium episodes.', + 'footer' => '{castopod} hosted on {host}', + ], +]; diff --git a/modules/PremiumPodcasts/Language/ko/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/ko/PremiumPodcasts.php new file mode 100644 index 00000000..18c0dd4e --- /dev/null +++ b/modules/PremiumPodcasts/Language/ko/PremiumPodcasts.php @@ -0,0 +1,34 @@ + 'Podcast contains premium episodes', + 'episode_is_premium' => 'Episode is premium, only available to premium subscribers', + 'unlock_episode' => 'This episode is for premium subscribers only. Click to unlock it!', + 'banner_unlock' => 'This podcast contains premium episodes, only available to premium subscribers.', + 'banner_lock' => 'Podcast is unlocked, enjoy the premium episodes!', + 'subscribe' => 'Subscribe', + 'lock' => 'Lock', + 'unlock' => 'Unlock', + 'unlock_form' => [ + 'title' => 'Premium content', + 'subtitle' => 'This podcast contains locked premium episodes! Do you have the key to unlock them?', + 'token' => 'Enter your key', + 'token_hint' => 'If you are subscribed to {podcastTitle}, you may copy the key that was sent to you via email and paste it here.', + 'submit' => 'Unlock all episodes!', + 'call_to_action' => 'Unlock all episodes of {podcastTitle}:', + 'subscribe_cta' => 'Subscribe now!', + ], + 'messages' => [ + 'unlockSuccess' => 'Podcast was successfully unlocked! Enjoy the premium episodes!', + 'unlockBadAttempt' => 'Your key does not seem to be working…', + 'lockSuccess' => 'Podcast was successfully locked!', + ], +]; diff --git a/modules/PremiumPodcasts/Language/ko/Subscription.php b/modules/PremiumPodcasts/Language/ko/Subscription.php new file mode 100644 index 00000000..e83f0cb2 --- /dev/null +++ b/modules/PremiumPodcasts/Language/ko/Subscription.php @@ -0,0 +1,100 @@ + 'Podcast subscriptions', + 'add' => 'New subscription', + 'view' => 'View subscription', + 'edit' => 'Edit subscription', + 'regenerate_token' => 'Regenerate token', + 'suspend' => 'Suspend subscription', + 'resume' => 'Resume subscription', + 'delete' => 'Delete subscription', + 'status' => [ + 'active' => 'Active', + 'suspended' => 'Suspended', + 'expired' => 'Expired', + ], + 'list' => [ + 'number' => 'Number', + 'email' => 'Email', + 'expiration_date' => 'Expiration date', + 'unlimited' => 'Unlimited', + 'downloads' => 'Downloads', + 'status' => 'Status', + ], + 'form' => [ + 'email' => 'Email', + 'expiration_date' => 'Expiration date', + 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', + 'submit_create' => 'Create subscription', + 'submit_edit' => 'Edit subscription', + ], + 'form_link_add' => [ + 'link' => 'Subscription page link', + 'link_hint' => 'This will add a call to action in the website inviting listeners to subscribe to the podcast.', + 'submit' => 'Save link', + ], + 'suspend_form' => [ + 'disclaimer' => 'Suspending the subscription will restrict the subscriber from having access to the premium content. You will still be able to lift the suspension afterwards.', + 'reason' => 'Reason', + 'reason_placeholder' => 'Why are you suspending the subscription?', + "submit" => 'Suspend subscription', + ], + 'delete_form' => [ + 'disclaimer' => 'Deleting {subscriber}\'s subscription will remove all analytics data associated with it.', + 'understand' => 'I understand, remove the subscription permanently', + 'submit' => 'Remove subscription', + ], + 'messages' => [ + 'addSuccess' => 'New subscription added! A welcome email was sent to {subscriber}.', + 'addError' => 'Subscription could not be added.', + 'editSuccess' => 'Subscription expiry date was updated! An email was sent to {subscriber}.', + 'editError' => 'Subscription could not be edited.', + 'regenerateTokenSuccess' => 'Token regenerated! An email was sent to {subscriber} with the new token.', + 'regenerateTokenError' => 'Token could not be regenerated.', + 'deleteSuccess' => 'Subscription was removed! An email was sent to {subscriber}.', + 'deleteError' => 'Subscription could not be removed.', + 'suspendSuccess' => 'Subscription was suspended! An email was sent to {subscriber}.', + 'suspendError' => 'Subscription could not be suspended.', + 'resumeSuccess' => 'Subscription was resumed! An email was sent to {subscriber}.', + 'resumeError' => 'Subscription could not be resumed.', + 'linkSaveSuccess' => 'Subscription link was saved successfully! It will appear in the website as a Call To Action!', + 'linkRemoveSuccess' => 'Subscription link was removed successfully!', + ], + 'emails' => [ + 'greeting' => 'Hey,', + 'token' => 'Your token: {0}', + 'unique_feed_link' => 'Your unique feed link: {0}', + 'how_to_use' => 'How to use?', + 'two_ways' => 'You have two ways of unlocking the premium episodes:', + 'import_into_app' => 'Copy your unique feed url inside your favourite podcast app (import it as a private feed to prevent exposing your credentials).', + 'go_to_website' => 'Go to {podcastWebsite}\'s website and unlock the podcast with your token.', + 'welcome_subject' => 'Welcome to {podcastTitle}', + 'welcome' => 'You have subscribed to {podcastTitle}, thank you and welcome aboard!', + 'welcome_token_title' => 'Here are your credentials to unlock the podcast\'s premium episodes:', + 'welcome_expires' => 'Your subscription was set to expire on {0}.', + 'welcome_never_expires' => 'Your subscription was set to never expire.', + 'reset_subject' => 'Your token was reset!', + 'reset_token' => 'Your access to {podcastTitle} has been reset!', + 'reset_token_title' => 'New credentials have been generated for you to unlock the podcast\'s premium episodes:', + 'edited_subject' => 'Your subscription has been updated!', + 'edited_expires' => 'Your subscription for {podcastTitle} was set to expire on {expiresAt}.', + 'edited_never_expires' => 'Your subscription for {podcastTitle} was set to never expire!', + 'suspended_subject' => 'Your subscription has been suspended!', + 'suspended' => 'Your subscription for {podcastTitle} has been suspended! You can no longer access the podcast\'s premium episodes.', + 'suspended_reason' => 'That is for the following reason: {0}', + 'resumed_subject' => 'Your subscription has been resumed!', + 'resumed' => 'Your subscription for {podcastTitle} has been resumed! You may access the podcast\'s premium episodes again.', + 'deleted_subject' => 'Your subscription has been removed!', + 'deleted' => 'Your subscription for {podcastTitle} has been removed! You no longer have access to the podcast\'s premium episodes.', + 'footer' => '{castopod} hosted on {host}', + ], +]; diff --git a/modules/PremiumPodcasts/Language/nl/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/nl/PremiumPodcasts.php index 18c0dd4e..8c05e623 100644 --- a/modules/PremiumPodcasts/Language/nl/PremiumPodcasts.php +++ b/modules/PremiumPodcasts/Language/nl/PremiumPodcasts.php @@ -9,26 +9,26 @@ declare(strict_types=1); */ return [ - 'podcast_is_premium' => 'Podcast contains premium episodes', - 'episode_is_premium' => 'Episode is premium, only available to premium subscribers', - 'unlock_episode' => 'This episode is for premium subscribers only. Click to unlock it!', - 'banner_unlock' => 'This podcast contains premium episodes, only available to premium subscribers.', - 'banner_lock' => 'Podcast is unlocked, enjoy the premium episodes!', - 'subscribe' => 'Subscribe', - 'lock' => 'Lock', - 'unlock' => 'Unlock', + 'podcast_is_premium' => 'Podcast bevat premium afleveringen', + 'episode_is_premium' => 'Aflevering is premium, alleen beschikbaar voor premium abonnees', + 'unlock_episode' => 'Deze aflevering is alleen voor premium abonnees. Klik om te ontgrendelen!', + 'banner_unlock' => 'Deze podcast bevat premium afleveringen, alleen beschikbaar voor premium abonnees.', + 'banner_lock' => 'Podcast is ontgrendeld, geniet van de premium afleveringen!', + 'subscribe' => 'Abonneren', + 'lock' => 'Vergrendel', + 'unlock' => 'Ontgrendel', 'unlock_form' => [ - 'title' => 'Premium content', - 'subtitle' => 'This podcast contains locked premium episodes! Do you have the key to unlock them?', - 'token' => 'Enter your key', - 'token_hint' => 'If you are subscribed to {podcastTitle}, you may copy the key that was sent to you via email and paste it here.', - 'submit' => 'Unlock all episodes!', - 'call_to_action' => 'Unlock all episodes of {podcastTitle}:', - 'subscribe_cta' => 'Subscribe now!', + 'title' => 'Premium inhoud', + 'subtitle' => 'Deze podcast bevat vergrendelde premium afleveringen! Heb je de sleutel om ze te ontgrendelen?', + 'token' => 'Voer jouw sleutel in', + 'token_hint' => 'Als je geabonneerd bent op {podcastTitle}, kun je de sleutel die naar je verzonden is via e-mail kopiëren en deze hier plakken.', + 'submit' => 'Ontgrendel alle afleveringen!', + 'call_to_action' => 'Ontgrendel alle afleveringen van {podcastTitle}:', + 'subscribe_cta' => 'Nu abonneren!', ], 'messages' => [ - 'unlockSuccess' => 'Podcast was successfully unlocked! Enjoy the premium episodes!', - 'unlockBadAttempt' => 'Your key does not seem to be working…', - 'lockSuccess' => 'Podcast was successfully locked!', + 'unlockSuccess' => 'Podcast is succesvol ontgrendeld! Geniet van de premium afleveringen!', + 'unlockBadAttempt' => 'Jouw sleutel lijkt niet te werken…', + 'lockSuccess' => 'Podcast is succesvol vergrendeld!', ], ]; diff --git a/modules/PremiumPodcasts/Language/nl/Subscription.php b/modules/PremiumPodcasts/Language/nl/Subscription.php index f8af256f..36bc8976 100644 --- a/modules/PremiumPodcasts/Language/nl/Subscription.php +++ b/modules/PremiumPodcasts/Language/nl/Subscription.php @@ -9,92 +9,92 @@ declare(strict_types=1); */ return [ - 'podcast_subscriptions' => 'Podcast subscriptions', - 'add' => 'New subscription', - 'view' => 'View subscription', - 'edit' => 'Edit subscription', - 'regenerate_token' => 'Regenerate token', - 'suspend' => 'Suspend subscription', - 'resume' => 'Resume subscription', - 'delete' => 'Delete subscription', + 'podcast_subscriptions' => 'Podcast abonnementen', + 'add' => 'Nieuw abonnement', + 'view' => 'Bekijk abonnement', + 'edit' => 'Bewerk abonnement', + 'regenerate_token' => 'Sleutel opnieuw genereren', + 'suspend' => 'Abonnement opschorten', + 'resume' => 'Abonnement hervatten', + 'delete' => 'Abonnement verwijderen', 'status' => [ - 'active' => 'Active', - 'suspended' => 'Suspended', - 'expired' => 'Expired', + 'active' => 'Actief', + 'suspended' => 'Opgeschort', + 'expired' => 'Verlopen', ], 'list' => [ - 'number' => 'Number', - 'email' => 'Email', - 'expiration_date' => 'Expiration date', - 'unlimited' => 'Unlimited', + 'number' => 'Nummer', + 'email' => 'E-mail', + 'expiration_date' => 'Vervaldatum', + 'unlimited' => 'Onbeperkt', 'downloads' => 'Downloads', 'status' => 'Status', ], 'form' => [ - 'email' => 'Email', - 'expiration_date' => 'Expiration date', - 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', - 'submit_edit' => 'Edit subscription', + 'email' => 'E-mail', + 'expiration_date' => 'Vervaldatum', + 'expiration_date_hint' => 'De datum en tijd waarop het abonnement verloopt. Laat leeg voor een onbeperkt abonnement.', + 'submit_create' => 'Abonnement aanmaken', + 'submit_edit' => 'Bewerk abonnement', ], 'form_link_add' => [ - 'link' => 'Subscription page link', - 'link_hint' => 'This will add a call to action in the website inviting listeners to subscribe to the podcast.', - 'submit' => 'Save link', + 'link' => 'Abonnementspagina link', + 'link_hint' => 'Dit zal een call-to-action toevoegen op de website die luisteraars uitnodigt om zich te abonneren op de podcast.', + 'submit' => 'Link opslaan', ], 'suspend_form' => [ - 'disclaimer' => 'Suspending the subscription will restrict the subscriber from having access to the premium content. You will still be able to lift the suspension afterwards.', - 'reason' => 'Reason', - 'reason_placeholder' => 'Why are you suspending the subscription?', - "submit" => 'Suspend subscription', + 'disclaimer' => 'Opschorting van het abonnement zorgt ervoor dat de abonnee geen toegang meer heeft tot premium inhoud. U kunt de opschorting op ieder moment opheffen.', + 'reason' => 'Reden', + 'reason_placeholder' => 'Waarom wilt u het abonnement onderbreken?', + "submit" => 'Abonnement opschorten', ], 'delete_form' => [ - 'disclaimer' => 'Deleting {subscriber}\'s subscription will remove all analytics data associated with it.', - 'understand' => 'I understand, remove the subscription permanently', - 'submit' => 'Remove subscription', + 'disclaimer' => 'Het verwijderen van het abonnement van {subscriber} zal alle statistieken ervan verwijderen.', + 'understand' => 'Ik begrijp het, verwijder het abonnement permanent', + 'submit' => 'Abonnement verwijderen', ], 'messages' => [ - 'addSuccess' => 'New subscription added! A welcome email was sent to {subscriber}.', - 'addError' => 'Subscription could not be added.', - 'editSuccess' => 'Subscription expiry date was updated! An email was sent to {subscriber}.', - 'editError' => 'Subscription could not be edited.', - 'regenerateTokenSuccess' => 'Token regenerated! An email was sent to {subscriber} with the new token.', - 'regenerateTokenError' => 'Token could not be regenerated.', - 'deleteSuccess' => 'Subscription was removed! An email was sent to {subscriber}.', - 'deleteError' => 'Subscription could not be removed.', - 'suspendSuccess' => 'Subscription was suspended! An email was sent to {subscriber}.', - 'suspendError' => 'Subscription could not be suspended.', - 'resumeSuccess' => 'Subscription was resumed! An email was sent to {subscriber}.', - 'resumeError' => 'Subscription could not be resumed.', - 'linkSaveSuccess' => 'Subscription link was saved successfully! It will appear in the website as a Call To Action!', - 'linkRemoveSuccess' => 'Subscription link was removed successfully!', + 'addSuccess' => 'Nieuw abonnement toegevoegd! Er is een welkomstmail is naar {subscriber} gestuurd.', + 'addError' => 'Abonnement kon niet worden toegevoegd.', + 'editSuccess' => 'Abonnement vervaldatum is bijgewerkt! Er is een e-mail verzonden naar {subscriber}.', + 'editError' => 'Abonnement kon niet worden bijgewerkt.', + 'regenerateTokenSuccess' => 'Token vernieuwd! Er is een e-mail verzonden naar {subscriber} met de nieuwe gegevens.', + 'regenerateTokenError' => 'Token kon niet worden vernieuwd.', + 'deleteSuccess' => 'Abonnement is verwijderd! Er is een e-mail verzonden naar {subscriber}.', + 'deleteError' => 'Abonnement kon niet worden verwijderd.', + 'suspendSuccess' => 'Abonnement is opgeschort! Er is een e-mail verzonden naar {subscriber}.', + 'suspendError' => 'Abonnement kon niet opgeschort worden.', + 'resumeSuccess' => 'Abonnement is hervat! Er is een e-mail verzonden naar {subscriber}.', + 'resumeError' => 'Abonnement kon niet worden hervat.', + 'linkSaveSuccess' => 'Abonnementlink is succesvol opgeslagen! Het zal verschijnen op de website als een call-to-action!', + 'linkRemoveSuccess' => 'Abonnementlink is succesvol verwijderd!', ], 'emails' => [ - 'greeting' => 'Hey,', - 'token' => 'Your token: {0}', - 'unique_feed_link' => 'Your unique feed link: {0}', - 'how_to_use' => 'How to use?', - 'two_ways' => 'You have two ways of unlocking the premium episodes:', - 'import_into_app' => 'Copy your unique feed url inside your favourite podcast app (import it as a private feed to prevent exposing your credentials).', - 'go_to_website' => 'Go to {podcastWebsite}\'s website and unlock the podcast with your token.', - 'welcome_subject' => 'Welcome to {podcastTitle}', - 'welcome' => 'You have subscribed to {podcastTitle}, thank you and welcome aboard!', - 'welcome_token_title' => 'Here are your credentials to unlock the podcast\'s premium episodes:', - 'welcome_expires' => 'Your subscription was set to expire on {0}.', - 'welcome_never_expires' => 'Your subscription was set to never expire.', - 'reset_subject' => 'Your token was reset!', - 'reset_token' => 'Your access to {podcastTitle} has been reset!', - 'reset_token_title' => 'New credentials have been generated for you to unlock the podcast\'s premium episodes:', - 'edited_subject' => 'Your subscription has been updated!', - 'edited_expires' => 'Your subscription for {podcastTitle} was set to expire on {expiresAt}.', - 'edited_never_expires' => 'Your subscription for {podcastTitle} was set to never expire!', - 'suspended_subject' => 'Your subscription has been suspended!', - 'suspended' => 'Your subscription for {podcastTitle} has been suspended! You can no longer access the podcast\'s premium episodes.', - 'suspended_reason' => 'That is for the following reason: {0}', - 'resumed_subject' => 'Your subscription has been resumed!', - 'resumed' => 'Your subscription for {podcastTitle} has been resumed! You may access the podcast\'s premium episodes again.', - 'deleted_subject' => 'Your subscription has been removed!', - 'deleted' => 'Your subscription for {podcastTitle} has been removed! You no longer have access to the podcast\'s premium episodes.', - 'footer' => '{castopod} hosted on {host}', + 'greeting' => 'Hoi,', + 'token' => 'Uw token: {0}', + 'unique_feed_link' => 'Uw persoonlijke feed: {0}', + 'how_to_use' => 'Gebruiksaanwijzing', + 'two_ways' => 'Je hebt twee manieren om toegang te krijgen tot de premium afleveringen:', + 'import_into_app' => 'Kopieer uw persoonlijke feed in je favoriete podcast app (importeer deze als een privé feed om te voorkomen dat je inloggegevens worden gedeeld).', + 'go_to_website' => 'Ga naar de website van {podcastWebsite} en krijgt toegang tot de podcast met uw persoonlijke token.', + 'welcome_subject' => 'Welkom bij {podcastTitle}', + 'welcome' => 'Je bent geabonneerd op {podcastTitle}, bedankt en welkom bij de club!', + 'welcome_token_title' => 'Hier zijn uw inloggegevens om toegang te krijgen tot de premium afleveringen van de podcast:', + 'welcome_expires' => 'Uw abonnement verloopt op {0}.', + 'welcome_never_expires' => 'Uw abonnement stopt niet automatisch.', + 'reset_subject' => 'Je token is vernieuwd!', + 'reset_token' => 'Uw toegang tot {podcastTitle} is gereset!', + 'reset_token_title' => 'Nieuwe inloggegevens zijn gegenereerd om toegang tot de premium afleveringen van de podcast te krijgen:', + 'edited_subject' => 'Uw abonnement is bijgewerkt!', + 'edited_expires' => 'Je abonnement voor {podcastTitle} vervalt op {expiresAt}.', + 'edited_never_expires' => 'Je abonnement voor {podcastTitle} is ingesteld om niet automatisch te verlopen!', + 'suspended_subject' => 'Uw abonnement is opgeschort!', + 'suspended' => 'Uw abonnement voor {podcastTitle} is opgeschort! U heeft niet langer toegang tot de premium afleveringen van de podcast.', + 'suspended_reason' => 'Dat is gebeurd om de volgende reden: {0}', + 'resumed_subject' => 'Uw abonnement is hervat!', + 'resumed' => 'Uw abonnement op {podcastTitle} is hervat! U heeft weer toegang tot de premium afleveringen van de podcast.', + 'deleted_subject' => 'Uw abonnement is beëindigd!', + 'deleted' => 'Uw abonnement op {podcastTitle} is verwijderd! U heeft niet langer toegang tot de premium afleveringen van de podcast.', + 'footer' => '{castopod} gehost op {host}', ], ]; diff --git a/modules/PremiumPodcasts/Language/nn-no/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/nn-no/PremiumPodcasts.php new file mode 100644 index 00000000..4decf740 --- /dev/null +++ b/modules/PremiumPodcasts/Language/nn-no/PremiumPodcasts.php @@ -0,0 +1,34 @@ + 'Podkasten inneheld betalte episodar', + 'episode_is_premium' => 'Episoden er bak betalingsmur. Berre betalande abonnentar har tilgang', + 'unlock_episode' => 'Denne episoden er berre for betalande abonnentar. Klikk for å få tilgang!', + 'banner_unlock' => 'Denne podkasten inneheld episodar som berre er tilgjengelege for betalande abonnentar.', + 'banner_lock' => 'Podkasten er låst opp, så no kan du høyra på dei betalte episodane!', + 'subscribe' => 'Abonner', + 'lock' => 'Lås', + 'unlock' => 'Lås opp', + 'unlock_form' => [ + 'title' => 'Betalt innhald', + 'subtitle' => 'Denne podkasten inneheld episodar bak betalingsmur. Har du nykelen for å få tilgang til dei?', + 'token' => 'Skriv inn nykelen', + 'token_hint' => 'Viss du abonnerer på {podcastTitle}, kan du kopiera nykelen du fekk på epost og lima han inn her.', + 'submit' => 'Få tilgang til alle episodane!', + 'call_to_action' => 'Få tilgang til alle episodane av {podcastTitle}:', + 'subscribe_cta' => 'Abonner no!', + ], + 'messages' => [ + 'unlockSuccess' => 'Podkasten er låst opp. No kan du høyra på dei betalte episodane!', + 'unlockBadAttempt' => 'Nykelen din fungerer ikkje…', + 'lockSuccess' => 'Podkasten er låst.', + ], +]; diff --git a/modules/PremiumPodcasts/Language/nn-no/Subscription.php b/modules/PremiumPodcasts/Language/nn-no/Subscription.php new file mode 100644 index 00000000..d32055c8 --- /dev/null +++ b/modules/PremiumPodcasts/Language/nn-no/Subscription.php @@ -0,0 +1,100 @@ + 'Abonnement til podkasten', + 'add' => 'Nytt abonnement', + 'view' => 'Vis abonnementet', + 'edit' => 'Rediger abonnementet', + 'regenerate_token' => 'Regenerer nykel', + 'suspend' => 'Stopp abonnementet', + 'resume' => 'Start oppatt abonnementet', + 'delete' => 'Slett abonnementet', + 'status' => [ + 'active' => 'Aktiv', + 'suspended' => 'Stoppa', + 'expired' => 'Utgått', + ], + 'list' => [ + 'number' => 'Nummer', + 'email' => 'Epost', + 'expiration_date' => 'Gyldig til', + 'unlimited' => 'Uavgrensa', + 'downloads' => 'Nedlastingar', + 'status' => 'Status', + ], + 'form' => [ + 'email' => 'Epost', + 'expiration_date' => 'Gyldig til', + 'expiration_date_hint' => 'Datoen og tidspunket abonnementet stoppar. La det stå tomt viss abonnementet skal gå utan sluttdato.', + 'submit_create' => 'Opprett abonnement', + 'submit_edit' => 'Rediger abonnementet', + ], + 'form_link_add' => [ + 'link' => 'Lenke til abonnementssida', + 'link_hint' => 'Her legg du til ei oppmoding på nettsida di, der du inviterer lyttarane til å abonnera på podkasten din.', + 'submit' => 'Lagre lenka', + ], + 'suspend_form' => [ + 'disclaimer' => 'Viss du stoppar abonnementet, vil ikkje abonnenten lenger få tilgang til betalt innhald. Du kan starta abonnementet att seinare.', + 'reason' => 'Grunngjeving', + 'reason_placeholder' => 'Kvifor stoppar du abonnementet?', + "submit" => 'Stopp abonnementet', + ], + 'delete_form' => [ + 'disclaimer' => 'Viss du slettar abonnementet til {subscriber}, slettar du òg alle analysedata knytt til abonnementet.', + 'understand' => 'Eg forstår, slett abonnementet', + 'submit' => 'Slett abonnementet', + ], + 'messages' => [ + 'addSuccess' => 'Du har fått ein ny abonnent! Me har sendt ein velkomstepost til {subscriber}.', + 'addError' => 'Greidde ikkje leggja til abonnementet.', + 'editSuccess' => 'Stoppdatoen for abonnementet er oppdatert! Me har sendt ein epost til {subscriber}.', + 'editError' => 'Greidde ikkje redigera abonnementet.', + 'regenerateTokenSuccess' => 'Nykelen er regenerert! Me sende ein epost til {subscriber} med den nye nykelen.', + 'regenerateTokenError' => 'Greidde ikkje regenerera nykelen.', + 'deleteSuccess' => 'Abonnementet er sletta! Me sende ein epost til {subscriber}.', + 'deleteError' => 'Greidde ikkje sletta abonnementet.', + 'suspendSuccess' => 'Abonnementet vart stoppa! Me sende ein epost til {subscriber}.', + 'suspendError' => 'Greidde ikkje stoppa abonnementet.', + 'resumeSuccess' => 'Abonnementet er starta att! Me sende ein epost til {subscriber}.', + 'resumeError' => 'Greidde ikkje starta abonnementet att.', + 'linkSaveSuccess' => 'Abonnementslenka er lagra. Ho vil visa på nettstaden som ei handlingsvarsling.', + 'linkRemoveSuccess' => 'Abonnementslenka vart fjerna.', + ], + 'emails' => [ + 'greeting' => 'Hei', + 'token' => 'Nykelen din: {0}', + 'unique_feed_link' => 'Den unike lenka til straumen: {0}', + 'how_to_use' => 'Korleis skal eg bruka dette?', + 'two_ways' => 'Du kan låsa opp betalte episodar på to måtar:', + 'import_into_app' => 'Kopier den unike adressa til podkaststraumen til favoritt-podkastappen din (importer adressa som ein privat straum slik at du ikkje avslører innloggingsopplysingane dine).', + 'go_to_website' => 'Gå til heimesida til {podcastWebsite} og lås opp podkasten med nykelen.', + 'welcome_subject' => 'Velkomen til {podcastTitle}', + 'welcome' => 'Du abonnerer på {podcastTitle}. Takk, og velkomen ombord!', + 'welcome_token_title' => 'Her er nykelen for å få tilgang til dei betalte episodane til podkasten:', + 'welcome_expires' => 'Abonnementet ditt hadde stoppdato {0}.', + 'welcome_never_expires' => 'Abonnementet ditt hadde ingen stoppdato.', + 'reset_subject' => 'Nykelen din er nullstilt!', + 'reset_token' => 'Tilgangen din til {podcastTitle} er nullstilt!', + 'reset_token_title' => 'Me har laga nye tilgangsopplysingar for deg for å få tilgang til dei betalte episodane til podkasten:', + 'edited_subject' => 'Abonnementet ditt er oppdatert!', + 'edited_expires' => 'Abonnementet ditt på {podcastTitle} hadde stoppdato {expiresAt}.', + 'edited_never_expires' => 'Abonnementet ditt på {podcastTitle} hadde ingen stoppdato!', + 'suspended_subject' => 'Abonnementet ditt er stoppa!', + 'suspended' => 'Abonnementet ditt på {podcastTitle} er stoppa! Du har ikkje lenger tilgang til dei betalte episodane til denne podkasten.', + 'suspended_reason' => 'Det er av desse grunnane: {0}', + 'resumed_subject' => 'Abonnementet ditt har starta att!', + 'resumed' => 'Abonnementet ditt på {podcastTitle} har starta att! Du har tilgang til dei betalte episodane til denne podkasten.', + 'deleted_subject' => 'Abonnementet ditt er sletta!', + 'deleted' => 'Abonnementet ditt på {podcastTitle} er sletta! Du har ikkje lenger tilgang til dei betalte episodane til podkasten.', + 'footer' => '{castopod} køyrer på {host}', + ], +]; diff --git a/modules/PremiumPodcasts/Language/oc/Subscription.php b/modules/PremiumPodcasts/Language/oc/Subscription.php index f8af256f..e83f0cb2 100644 --- a/modules/PremiumPodcasts/Language/oc/Subscription.php +++ b/modules/PremiumPodcasts/Language/oc/Subscription.php @@ -34,7 +34,7 @@ return [ 'email' => 'Email', 'expiration_date' => 'Expiration date', 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', + 'submit_create' => 'Create subscription', 'submit_edit' => 'Edit subscription', ], 'form_link_add' => [ diff --git a/modules/PremiumPodcasts/Language/pl/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/pl/PremiumPodcasts.php index 18c0dd4e..d9a3d493 100644 --- a/modules/PremiumPodcasts/Language/pl/PremiumPodcasts.php +++ b/modules/PremiumPodcasts/Language/pl/PremiumPodcasts.php @@ -9,26 +9,26 @@ declare(strict_types=1); */ return [ - 'podcast_is_premium' => 'Podcast contains premium episodes', - 'episode_is_premium' => 'Episode is premium, only available to premium subscribers', - 'unlock_episode' => 'This episode is for premium subscribers only. Click to unlock it!', - 'banner_unlock' => 'This podcast contains premium episodes, only available to premium subscribers.', - 'banner_lock' => 'Podcast is unlocked, enjoy the premium episodes!', - 'subscribe' => 'Subscribe', - 'lock' => 'Lock', - 'unlock' => 'Unlock', + 'podcast_is_premium' => 'Podcast zawiera odcinki premium', + 'episode_is_premium' => 'Ten odcinek premium jest dostępny tylko dla subskrybentów', + 'unlock_episode' => 'Ten odcinek jest tylko dla subskrybentów premium. Kliknij, aby go odblokować!', + 'banner_unlock' => 'Ten podcast zawiera odcinki premium, dostępne tylko dla subskrybentów premium.', + 'banner_lock' => 'Podcast został odblokowany, ciesz się odcinkami premium!', + 'subscribe' => 'Subskrybuj', + 'lock' => 'Zablokuj', + 'unlock' => 'Odblokuj', 'unlock_form' => [ - 'title' => 'Premium content', - 'subtitle' => 'This podcast contains locked premium episodes! Do you have the key to unlock them?', - 'token' => 'Enter your key', - 'token_hint' => 'If you are subscribed to {podcastTitle}, you may copy the key that was sent to you via email and paste it here.', - 'submit' => 'Unlock all episodes!', - 'call_to_action' => 'Unlock all episodes of {podcastTitle}:', - 'subscribe_cta' => 'Subscribe now!', + 'title' => 'Zawartość premium', + 'subtitle' => 'Ten podcast zawiera zablokowane odcinki premium! Czy masz klucz do ich odblokowania?', + 'token' => 'Wprowadź swój klucz', + 'token_hint' => 'Jeśli subskrybujesz {podcastTitle}, możesz skopiować klucz wysłany do Ciebie mailem i wkleić go tutaj.', + 'submit' => 'Odblokuj wszystkie odcinki!', + 'call_to_action' => 'Odblokuj wszystkie odcinki {podcastTitle}:', + 'subscribe_cta' => 'Subskrybuj teraz!', ], 'messages' => [ - 'unlockSuccess' => 'Podcast was successfully unlocked! Enjoy the premium episodes!', - 'unlockBadAttempt' => 'Your key does not seem to be working…', - 'lockSuccess' => 'Podcast was successfully locked!', + 'unlockSuccess' => 'Podcast został pomyślnie odblokowany! Ciesz się odcinkami premium!', + 'unlockBadAttempt' => 'Twój klucz nie działa…', + 'lockSuccess' => 'Podcast został pomyślnie zablokowany!', ], ]; diff --git a/modules/PremiumPodcasts/Language/pl/Subscription.php b/modules/PremiumPodcasts/Language/pl/Subscription.php index f8af256f..8a81eca8 100644 --- a/modules/PremiumPodcasts/Language/pl/Subscription.php +++ b/modules/PremiumPodcasts/Language/pl/Subscription.php @@ -9,92 +9,92 @@ declare(strict_types=1); */ return [ - 'podcast_subscriptions' => 'Podcast subscriptions', - 'add' => 'New subscription', - 'view' => 'View subscription', - 'edit' => 'Edit subscription', - 'regenerate_token' => 'Regenerate token', - 'suspend' => 'Suspend subscription', - 'resume' => 'Resume subscription', - 'delete' => 'Delete subscription', + 'podcast_subscriptions' => 'Subskrypcje podcastu', + 'add' => 'Nowa subskrypcja', + 'view' => 'Wyświetl subskrypcję', + 'edit' => 'Edytuj subskrypcję', + 'regenerate_token' => 'Wygeneruj nowy token', + 'suspend' => 'Wstrzymaj subskrypcję', + 'resume' => 'Wznów subskrypcję', + 'delete' => 'Usuń subskrypcję', 'status' => [ - 'active' => 'Active', - 'suspended' => 'Suspended', - 'expired' => 'Expired', + 'active' => 'Aktywne', + 'suspended' => 'Zawieszony', + 'expired' => 'Wygasły', ], 'list' => [ - 'number' => 'Number', + 'number' => 'Numer', 'email' => 'Email', - 'expiration_date' => 'Expiration date', - 'unlimited' => 'Unlimited', - 'downloads' => 'Downloads', + 'expiration_date' => 'Data ważności', + 'unlimited' => 'Nielimitowany', + 'downloads' => 'Pobrane', 'status' => 'Status', ], 'form' => [ 'email' => 'Email', - 'expiration_date' => 'Expiration date', - 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', - 'submit_edit' => 'Edit subscription', + 'expiration_date' => 'Data ważności', + 'expiration_date_hint' => 'Data i godzina wygaśnięcia subskrypcji. Pozostaw puste dla nieograniczonej subskrypcji.', + 'submit_create' => 'Utwórz subskrypcję', + 'submit_edit' => 'Edytuj subskrypcję', ], 'form_link_add' => [ - 'link' => 'Subscription page link', - 'link_hint' => 'This will add a call to action in the website inviting listeners to subscribe to the podcast.', - 'submit' => 'Save link', + 'link' => 'Link do strony subskrypcji', + 'link_hint' => 'Spowoduje to dodanie przycisku zapraszającego słuchaczy do subskrypcji podcastu.', + 'submit' => 'Zapisz link', ], 'suspend_form' => [ - 'disclaimer' => 'Suspending the subscription will restrict the subscriber from having access to the premium content. You will still be able to lift the suspension afterwards.', - 'reason' => 'Reason', - 'reason_placeholder' => 'Why are you suspending the subscription?', - "submit" => 'Suspend subscription', + 'disclaimer' => 'Zawieszenie subskrypcji ograniczy subskrybentowi dostęp do treści premium. Wciąż będziesz mógł później cofnąć zawieszenie.', + 'reason' => 'Powód', + 'reason_placeholder' => 'Dlaczego zawieszasz subskrypcję?', + "submit" => 'Wstrzymaj subskrypcję', ], 'delete_form' => [ - 'disclaimer' => 'Deleting {subscriber}\'s subscription will remove all analytics data associated with it.', - 'understand' => 'I understand, remove the subscription permanently', - 'submit' => 'Remove subscription', + 'disclaimer' => 'Usunięcie subskrypcji {subscriber} spowoduje usunięcie wszystkich danych analitycznych z nią związanych.', + 'understand' => 'Rozumiem, usuń subskrypcję na stałe', + 'submit' => 'Usuń subskrypcję', ], 'messages' => [ - 'addSuccess' => 'New subscription added! A welcome email was sent to {subscriber}.', - 'addError' => 'Subscription could not be added.', - 'editSuccess' => 'Subscription expiry date was updated! An email was sent to {subscriber}.', - 'editError' => 'Subscription could not be edited.', - 'regenerateTokenSuccess' => 'Token regenerated! An email was sent to {subscriber} with the new token.', - 'regenerateTokenError' => 'Token could not be regenerated.', - 'deleteSuccess' => 'Subscription was removed! An email was sent to {subscriber}.', - 'deleteError' => 'Subscription could not be removed.', - 'suspendSuccess' => 'Subscription was suspended! An email was sent to {subscriber}.', - 'suspendError' => 'Subscription could not be suspended.', - 'resumeSuccess' => 'Subscription was resumed! An email was sent to {subscriber}.', - 'resumeError' => 'Subscription could not be resumed.', - 'linkSaveSuccess' => 'Subscription link was saved successfully! It will appear in the website as a Call To Action!', - 'linkRemoveSuccess' => 'Subscription link was removed successfully!', + 'addSuccess' => 'Dodano nową subskrypcję! Wiadomość powitalna została wysłana na adres {subscriber}.', + 'addError' => 'Subskrypcja nie mogła zostać dodana.', + 'editSuccess' => 'Data wygaśnięcia subskrypcji została zaktualizowana! Wiadomość e-mail została wysłana do {subscriber}.', + 'editError' => 'Subskrypcja nie mogła być edytowana.', + 'regenerateTokenSuccess' => 'Token został zresetowany! Wiadomość e-mail została wysłana do {subscriber} z nowym tokenem.', + 'regenerateTokenError' => 'Token nie mógł zostać zresetowany.', + 'deleteSuccess' => 'Subskrypcja została usunięta! Wiadomość e-mail została wysłana do {subscriber}.', + 'deleteError' => 'Subskrypcja nie mogła zostać usunięta.', + 'suspendSuccess' => 'Subskrypcja została zawieszona! Wiadomość e-mail została wysłana do {subscriber}.', + 'suspendError' => 'Subskrypcja nie mogła zostać zawieszona.', + 'resumeSuccess' => 'Subskrypcja została wznowiona! Wiadomość e-mail została wysłana do {subscriber}.', + 'resumeError' => 'Subskrypcja nie mogła zostać wznowiona.', + 'linkSaveSuccess' => 'Link do subskrypcji został pomyślnie zapisany! Pojawi się na stronie internetowej jako Call To Action!', + 'linkRemoveSuccess' => 'Link do subskrypcji został pomyślnie usunięty!', ], 'emails' => [ - 'greeting' => 'Hey,', - 'token' => 'Your token: {0}', - 'unique_feed_link' => 'Your unique feed link: {0}', - 'how_to_use' => 'How to use?', - 'two_ways' => 'You have two ways of unlocking the premium episodes:', - 'import_into_app' => 'Copy your unique feed url inside your favourite podcast app (import it as a private feed to prevent exposing your credentials).', - 'go_to_website' => 'Go to {podcastWebsite}\'s website and unlock the podcast with your token.', - 'welcome_subject' => 'Welcome to {podcastTitle}', - 'welcome' => 'You have subscribed to {podcastTitle}, thank you and welcome aboard!', - 'welcome_token_title' => 'Here are your credentials to unlock the podcast\'s premium episodes:', - 'welcome_expires' => 'Your subscription was set to expire on {0}.', - 'welcome_never_expires' => 'Your subscription was set to never expire.', - 'reset_subject' => 'Your token was reset!', - 'reset_token' => 'Your access to {podcastTitle} has been reset!', - 'reset_token_title' => 'New credentials have been generated for you to unlock the podcast\'s premium episodes:', - 'edited_subject' => 'Your subscription has been updated!', - 'edited_expires' => 'Your subscription for {podcastTitle} was set to expire on {expiresAt}.', - 'edited_never_expires' => 'Your subscription for {podcastTitle} was set to never expire!', - 'suspended_subject' => 'Your subscription has been suspended!', - 'suspended' => 'Your subscription for {podcastTitle} has been suspended! You can no longer access the podcast\'s premium episodes.', - 'suspended_reason' => 'That is for the following reason: {0}', - 'resumed_subject' => 'Your subscription has been resumed!', - 'resumed' => 'Your subscription for {podcastTitle} has been resumed! You may access the podcast\'s premium episodes again.', - 'deleted_subject' => 'Your subscription has been removed!', - 'deleted' => 'Your subscription for {podcastTitle} has been removed! You no longer have access to the podcast\'s premium episodes.', - 'footer' => '{castopod} hosted on {host}', + 'greeting' => 'Cześć,', + 'token' => 'Twój token: {0}', + 'unique_feed_link' => 'Twój unikalny link do kanału: {0}', + 'how_to_use' => 'Jak używać?', + 'two_ways' => 'Masz dwa sposoby odblokowania odcinków premium:', + 'import_into_app' => 'Wklej swój unikalny adres URL kanału do swojej ulubionej aplikacji podcastowej (zaimportuj go jako prywatny kanał, aby zapobiec ujawnianiu Twoich tokenów).', + 'go_to_website' => 'Przejdź do strony {podcastWebsite} i odblokuj podcast za pomocą swojego tokenu.', + 'welcome_subject' => 'Witaj w {podcastTitle}', + 'welcome' => 'Zasubskrybowano {podcastTitle}, dziękuję i witam na pokładzie!', + 'welcome_token_title' => 'Oto twoje dane, aby odblokować odcinki premium podcastu:', + 'welcome_expires' => 'Twoja subskrypcja wygaśnie w dniu {0}.', + 'welcome_never_expires' => 'Twoja subskrypcja nigdy nie wygasa.', + 'reset_subject' => 'Twój token został zresetowany!', + 'reset_token' => 'Twój dostęp do {podcastTitle} został zresetowany!', + 'reset_token_title' => 'Wygenerowano nowe dane dostępowe do odblokowania odcinków premium podcastu:', + 'edited_subject' => 'Subskrypcja została zaktualizowana!', + 'edited_expires' => 'Twoja subskrypcja dla {podcastTitle} wygaśnie w dniu {expiresAt}.', + 'edited_never_expires' => 'Twoja subskrypcja dla {podcastTitle} nigdy nie wygaśnie!', + 'suspended_subject' => 'Twoja subskrypcja została zawieszona!', + 'suspended' => 'Twoja subskrypcja dla {podcastTitle} została zawieszona! Nie masz już dostępu do odcinków premium podcastu.', + 'suspended_reason' => 'Z następującego powodu: {0}', + 'resumed_subject' => 'Twoja subskrypcja została wznowiona!', + 'resumed' => 'Twoja subskrypcja dla {podcastTitle} została wznowiona! Masz ponownie dostęp do odcinków premium podcastu.', + 'deleted_subject' => 'Twoja subskrypcja została usunięta!', + 'deleted' => 'Twoja subskrypcja dla {podcastTitle} została usunięta! Nie masz już dostępu do odcinków premium podcastu.', + 'footer' => '{castopod} hostowane na {host}', ], ]; diff --git a/modules/PremiumPodcasts/Language/pt-br/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/pt-br/PremiumPodcasts.php new file mode 100644 index 00000000..b85012d9 --- /dev/null +++ b/modules/PremiumPodcasts/Language/pt-br/PremiumPodcasts.php @@ -0,0 +1,34 @@ + 'O Podcast contém episódios premium', + 'episode_is_premium' => 'Episódio é premium, somente disponível para assinantes premium', + 'unlock_episode' => 'Este episódio é apenas para assinantes premium. Clique para desbloqueá-lo!', + 'banner_unlock' => 'Este podcast contém episódios premium, somente disponível para assinantes premium.', + 'banner_lock' => 'O Podcast desbloqueado, aproveite os episódios premium!', + 'subscribe' => 'Inscrever-se', + 'lock' => 'Bloquear', + 'unlock' => 'Desbloquear', + 'unlock_form' => [ + 'title' => 'Conteúdo premium', + 'subtitle' => 'Este podcast contém episódios premium bloqueados! Você tem a chave para desbloqueá-los?', + 'token' => 'Digite sua chave', + 'token_hint' => 'Se você se inscreveu no {podcastTitle}, você pode copiar a chave que foi enviada por email e colá-la aqui.', + 'submit' => 'Desbloquear todos os episódios!', + 'call_to_action' => 'Desbloquear todos os episódios de {podcastTitle}:', + 'subscribe_cta' => 'Inscreva-se agora!', + ], + 'messages' => [ + 'unlockSuccess' => 'Podcast foi desbloqueado com sucesso! Aproveite os episódios premium!', + 'unlockBadAttempt' => 'Sua chave parece não estar funcionando…', + 'lockSuccess' => 'Podcast foi trancado com sucesso!', + ], +]; diff --git a/modules/PremiumPodcasts/Language/pt-br/Subscription.php b/modules/PremiumPodcasts/Language/pt-br/Subscription.php new file mode 100644 index 00000000..5e125494 --- /dev/null +++ b/modules/PremiumPodcasts/Language/pt-br/Subscription.php @@ -0,0 +1,100 @@ + 'Assinaturas de Podcast', + 'add' => 'Nova assinatura', + 'view' => 'Visualizar assinatura', + 'edit' => 'Editar assinatura', + 'regenerate_token' => 'Regerar token', + 'suspend' => 'Suspender assinatura', + 'resume' => 'Retomar assinatura', + 'delete' => 'Excluir assinatura', + 'status' => [ + 'active' => 'Ativo', + 'suspended' => 'Suspenso', + 'expired' => 'Expirado', + ], + 'list' => [ + 'number' => 'Número', + 'email' => 'E-mail', + 'expiration_date' => 'Data da expiração', + 'unlimited' => 'Sem limite', + 'downloads' => 'Downloads', + 'status' => 'Status', + ], + 'form' => [ + 'email' => 'E-mail', + 'expiration_date' => 'Data da expiração', + 'expiration_date_hint' => 'A data e hora em que a assinatura expirará. Deixe em branco para uma assinatura ilimitada.', + 'submit_create' => 'Criar assinatura', + 'submit_edit' => 'Editar assinatura', + ], + 'form_link_add' => [ + 'link' => 'Link da página de assinatura', + 'link_hint' => 'Isto irá adicionar uma chamada para a ação no site que convida ouvintes a se inscreverem no podcast.', + 'submit' => 'Salvar o link', + ], + 'suspend_form' => [ + 'disclaimer' => 'Suspending the subscription will restrict the subscriber from having access to the premium content. You will still be able to lift the suspension afterwards.', + 'reason' => 'Reason', + 'reason_placeholder' => 'Why are you suspending the subscription?', + "submit" => 'Suspend subscription', + ], + 'delete_form' => [ + 'disclaimer' => 'Deleting {subscriber}\'s subscription will remove all analytics data associated with it.', + 'understand' => 'I understand, remove the subscription permanently', + 'submit' => 'Remove subscription', + ], + 'messages' => [ + 'addSuccess' => 'New subscription added! A welcome email was sent to {subscriber}.', + 'addError' => 'Subscription could not be added.', + 'editSuccess' => 'Subscription expiry date was updated! An email was sent to {subscriber}.', + 'editError' => 'Subscription could not be edited.', + 'regenerateTokenSuccess' => 'Token regenerated! An email was sent to {subscriber} with the new token.', + 'regenerateTokenError' => 'Token could not be regenerated.', + 'deleteSuccess' => 'Subscription was removed! An email was sent to {subscriber}.', + 'deleteError' => 'Subscription could not be removed.', + 'suspendSuccess' => 'Subscription was suspended! An email was sent to {subscriber}.', + 'suspendError' => 'Subscription could not be suspended.', + 'resumeSuccess' => 'Subscription was resumed! An email was sent to {subscriber}.', + 'resumeError' => 'Subscription could not be resumed.', + 'linkSaveSuccess' => 'Subscription link was saved successfully! It will appear in the website as a Call To Action!', + 'linkRemoveSuccess' => 'Subscription link was removed successfully!', + ], + 'emails' => [ + 'greeting' => 'Hey,', + 'token' => 'Your token: {0}', + 'unique_feed_link' => 'Your unique feed link: {0}', + 'how_to_use' => 'How to use?', + 'two_ways' => 'You have two ways of unlocking the premium episodes:', + 'import_into_app' => 'Copy your unique feed url inside your favourite podcast app (import it as a private feed to prevent exposing your credentials).', + 'go_to_website' => 'Go to {podcastWebsite}\'s website and unlock the podcast with your token.', + 'welcome_subject' => 'Welcome to {podcastTitle}', + 'welcome' => 'You have subscribed to {podcastTitle}, thank you and welcome aboard!', + 'welcome_token_title' => 'Here are your credentials to unlock the podcast\'s premium episodes:', + 'welcome_expires' => 'Your subscription was set to expire on {0}.', + 'welcome_never_expires' => 'Your subscription was set to never expire.', + 'reset_subject' => 'Your token was reset!', + 'reset_token' => 'Your access to {podcastTitle} has been reset!', + 'reset_token_title' => 'New credentials have been generated for you to unlock the podcast\'s premium episodes:', + 'edited_subject' => 'Your subscription has been updated!', + 'edited_expires' => 'Your subscription for {podcastTitle} was set to expire on {expiresAt}.', + 'edited_never_expires' => 'Your subscription for {podcastTitle} was set to never expire!', + 'suspended_subject' => 'Your subscription has been suspended!', + 'suspended' => 'Your subscription for {podcastTitle} has been suspended! You can no longer access the podcast\'s premium episodes.', + 'suspended_reason' => 'That is for the following reason: {0}', + 'resumed_subject' => 'Your subscription has been resumed!', + 'resumed' => 'Your subscription for {podcastTitle} has been resumed! You may access the podcast\'s premium episodes again.', + 'deleted_subject' => 'Your subscription has been removed!', + 'deleted' => 'Your subscription for {podcastTitle} has been removed! You no longer have access to the podcast\'s premium episodes.', + 'footer' => '{castopod} hosted on {host}', + ], +]; diff --git a/modules/PremiumPodcasts/Language/pt/Subscription.php b/modules/PremiumPodcasts/Language/pt/Subscription.php index f8af256f..e83f0cb2 100644 --- a/modules/PremiumPodcasts/Language/pt/Subscription.php +++ b/modules/PremiumPodcasts/Language/pt/Subscription.php @@ -34,7 +34,7 @@ return [ 'email' => 'Email', 'expiration_date' => 'Expiration date', 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', + 'submit_create' => 'Create subscription', 'submit_edit' => 'Edit subscription', ], 'form_link_add' => [ diff --git a/modules/PremiumPodcasts/Language/ro/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/ro/PremiumPodcasts.php new file mode 100644 index 00000000..18c0dd4e --- /dev/null +++ b/modules/PremiumPodcasts/Language/ro/PremiumPodcasts.php @@ -0,0 +1,34 @@ + 'Podcast contains premium episodes', + 'episode_is_premium' => 'Episode is premium, only available to premium subscribers', + 'unlock_episode' => 'This episode is for premium subscribers only. Click to unlock it!', + 'banner_unlock' => 'This podcast contains premium episodes, only available to premium subscribers.', + 'banner_lock' => 'Podcast is unlocked, enjoy the premium episodes!', + 'subscribe' => 'Subscribe', + 'lock' => 'Lock', + 'unlock' => 'Unlock', + 'unlock_form' => [ + 'title' => 'Premium content', + 'subtitle' => 'This podcast contains locked premium episodes! Do you have the key to unlock them?', + 'token' => 'Enter your key', + 'token_hint' => 'If you are subscribed to {podcastTitle}, you may copy the key that was sent to you via email and paste it here.', + 'submit' => 'Unlock all episodes!', + 'call_to_action' => 'Unlock all episodes of {podcastTitle}:', + 'subscribe_cta' => 'Subscribe now!', + ], + 'messages' => [ + 'unlockSuccess' => 'Podcast was successfully unlocked! Enjoy the premium episodes!', + 'unlockBadAttempt' => 'Your key does not seem to be working…', + 'lockSuccess' => 'Podcast was successfully locked!', + ], +]; diff --git a/modules/PremiumPodcasts/Language/ro/Subscription.php b/modules/PremiumPodcasts/Language/ro/Subscription.php new file mode 100644 index 00000000..86a77ad8 --- /dev/null +++ b/modules/PremiumPodcasts/Language/ro/Subscription.php @@ -0,0 +1,100 @@ + 'Abonamente Podcast', + 'add' => 'Abonament nou', + 'view' => 'Vizualizare abonament', + 'edit' => 'Editare abonament', + 'regenerate_token' => 'Regenerează token de autentificare', + 'suspend' => 'Suspendare abonament', + 'resume' => 'Reluare abonament', + 'delete' => 'Şterge abonamentul', + 'status' => [ + 'active' => 'Activ', + 'suspended' => 'Suspendat', + 'expired' => 'Expirat', + ], + 'list' => [ + 'number' => 'Număr', + 'email' => 'E-mail', + 'expiration_date' => 'Data expirării', + 'unlimited' => 'Nelimitat', + 'downloads' => 'Descărcări', + 'status' => 'Status', + ], + 'form' => [ + 'email' => 'E-mail', + 'expiration_date' => 'Data expirării', + 'expiration_date_hint' => 'Data și ora la care expiră abonamentul. Lăsați gol pentru un abonament nelimitat.', + 'submit_create' => 'Create subscription', + 'submit_edit' => 'Editare abonament', + ], + 'form_link_add' => [ + 'link' => 'Link-ul paginii de abonare', + 'link_hint' => 'This will add a call to action in the website inviting listeners to subscribe to the podcast.', + 'submit' => 'Save link', + ], + 'suspend_form' => [ + 'disclaimer' => 'Suspending the subscription will restrict the subscriber from having access to the premium content. You will still be able to lift the suspension afterwards.', + 'reason' => 'Reason', + 'reason_placeholder' => 'Why are you suspending the subscription?', + "submit" => 'Suspend subscription', + ], + 'delete_form' => [ + 'disclaimer' => 'Deleting {subscriber}\'s subscription will remove all analytics data associated with it.', + 'understand' => 'I understand, remove the subscription permanently', + 'submit' => 'Remove subscription', + ], + 'messages' => [ + 'addSuccess' => 'New subscription added! A welcome email was sent to {subscriber}.', + 'addError' => 'Subscription could not be added.', + 'editSuccess' => 'Subscription expiry date was updated! An email was sent to {subscriber}.', + 'editError' => 'Subscription could not be edited.', + 'regenerateTokenSuccess' => 'Token regenerated! An email was sent to {subscriber} with the new token.', + 'regenerateTokenError' => 'Token could not be regenerated.', + 'deleteSuccess' => 'Subscription was removed! An email was sent to {subscriber}.', + 'deleteError' => 'Subscription could not be removed.', + 'suspendSuccess' => 'Subscription was suspended! An email was sent to {subscriber}.', + 'suspendError' => 'Subscription could not be suspended.', + 'resumeSuccess' => 'Subscription was resumed! An email was sent to {subscriber}.', + 'resumeError' => 'Subscription could not be resumed.', + 'linkSaveSuccess' => 'Subscription link was saved successfully! It will appear in the website as a Call To Action!', + 'linkRemoveSuccess' => 'Subscription link was removed successfully!', + ], + 'emails' => [ + 'greeting' => 'Hey,', + 'token' => 'Your token: {0}', + 'unique_feed_link' => 'Your unique feed link: {0}', + 'how_to_use' => 'How to use?', + 'two_ways' => 'You have two ways of unlocking the premium episodes:', + 'import_into_app' => 'Copy your unique feed url inside your favourite podcast app (import it as a private feed to prevent exposing your credentials).', + 'go_to_website' => 'Go to {podcastWebsite}\'s website and unlock the podcast with your token.', + 'welcome_subject' => 'Welcome to {podcastTitle}', + 'welcome' => 'You have subscribed to {podcastTitle}, thank you and welcome aboard!', + 'welcome_token_title' => 'Here are your credentials to unlock the podcast\'s premium episodes:', + 'welcome_expires' => 'Your subscription was set to expire on {0}.', + 'welcome_never_expires' => 'Your subscription was set to never expire.', + 'reset_subject' => 'Your token was reset!', + 'reset_token' => 'Your access to {podcastTitle} has been reset!', + 'reset_token_title' => 'New credentials have been generated for you to unlock the podcast\'s premium episodes:', + 'edited_subject' => 'Your subscription has been updated!', + 'edited_expires' => 'Your subscription for {podcastTitle} was set to expire on {expiresAt}.', + 'edited_never_expires' => 'Your subscription for {podcastTitle} was set to never expire!', + 'suspended_subject' => 'Your subscription has been suspended!', + 'suspended' => 'Your subscription for {podcastTitle} has been suspended! You can no longer access the podcast\'s premium episodes.', + 'suspended_reason' => 'That is for the following reason: {0}', + 'resumed_subject' => 'Your subscription has been resumed!', + 'resumed' => 'Your subscription for {podcastTitle} has been resumed! You may access the podcast\'s premium episodes again.', + 'deleted_subject' => 'Your subscription has been removed!', + 'deleted' => 'Your subscription for {podcastTitle} has been removed! You no longer have access to the podcast\'s premium episodes.', + 'footer' => '{castopod} hosted on {host}', + ], +]; diff --git a/modules/PremiumPodcasts/Language/ru/Subscription.php b/modules/PremiumPodcasts/Language/ru/Subscription.php index f8af256f..e83f0cb2 100644 --- a/modules/PremiumPodcasts/Language/ru/Subscription.php +++ b/modules/PremiumPodcasts/Language/ru/Subscription.php @@ -34,7 +34,7 @@ return [ 'email' => 'Email', 'expiration_date' => 'Expiration date', 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', + 'submit_create' => 'Create subscription', 'submit_edit' => 'Edit subscription', ], 'form_link_add' => [ diff --git a/modules/PremiumPodcasts/Language/sk/Subscription.php b/modules/PremiumPodcasts/Language/sk/Subscription.php index f8af256f..e83f0cb2 100644 --- a/modules/PremiumPodcasts/Language/sk/Subscription.php +++ b/modules/PremiumPodcasts/Language/sk/Subscription.php @@ -34,7 +34,7 @@ return [ 'email' => 'Email', 'expiration_date' => 'Expiration date', 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', + 'submit_create' => 'Create subscription', 'submit_edit' => 'Edit subscription', ], 'form_link_add' => [ diff --git a/modules/PremiumPodcasts/Language/sr-latn/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/sr-latn/PremiumPodcasts.php new file mode 100644 index 00000000..6da5e74e --- /dev/null +++ b/modules/PremiumPodcasts/Language/sr-latn/PremiumPodcasts.php @@ -0,0 +1,34 @@ + 'Podcast sadrži premijerne epizode', + 'episode_is_premium' => 'Epizoda je premijum, dostupna je samo premijum pretplatnicima', + 'unlock_episode' => 'Ova epizoda je samo za premijum pretplatnike. Klikni da je otključaš!', + 'banner_unlock' => 'Ovaj podkast sadrži premijum epizode, dostupne samo premijum pretplatnicima.', + 'banner_lock' => 'Podkast je otključan, uživajte u premijum epizodama!', + 'subscribe' => 'Pretplatite se', + 'lock' => 'Zaključaj', + 'unlock' => 'Otključaj', + 'unlock_form' => [ + 'title' => 'Premijum sadržaj', + 'subtitle' => 'Ovaj podkat sadrži zaključane premijum epizode! Da li imate ključ?', + 'token' => 'Unesite ključ', + 'token_hint' => 'Ako ste pretplaćeni na {podcastTitle}, možete kopirati ključ koji vam je poslat na E poštu i zalepiti ga ovde.', + 'submit' => 'Otključaj sve epizode!', + 'call_to_action' => 'Otključaj sve epizode {podcastTitle}:', + 'subscribe_cta' => 'Pretplati se sada!', + ], + 'messages' => [ + 'unlockSuccess' => 'Podkast je uspešno otključan, uživajte u premijum epizodama!', + 'unlockBadAttempt' => 'Izgleda da vaš ključ ne radi…', + 'lockSuccess' => 'Podkast je uspešno zaključan!', + ], +]; diff --git a/modules/PremiumPodcasts/Language/sr-latn/Subscription.php b/modules/PremiumPodcasts/Language/sr-latn/Subscription.php new file mode 100644 index 00000000..0fbeb3aa --- /dev/null +++ b/modules/PremiumPodcasts/Language/sr-latn/Subscription.php @@ -0,0 +1,100 @@ + 'Podkast pretplate', + 'add' => 'Nova pretplata', + 'view' => 'Pogledaj pretplatu', + 'edit' => 'Uredi pretplatu', + 'regenerate_token' => 'Regeneriši token', + 'suspend' => 'Ukini pretplatu', + 'resume' => 'Nastavi pretplatu', + 'delete' => 'Obriši pretplatu', + 'status' => [ + 'active' => 'Aktivno', + 'suspended' => 'Ukinuto', + 'expired' => 'Isteklo', + ], + 'list' => [ + 'number' => 'Broj', + 'email' => 'E-pošta', + 'expiration_date' => 'Datum Isteka', + 'unlimited' => 'Neograničeno', + 'downloads' => 'Preuzimanja', + 'status' => 'Status', + ], + 'form' => [ + 'email' => 'E-pošta', + 'expiration_date' => 'Datum Isteka', + 'expiration_date_hint' => 'Datum i vreme kada pretplata ističe. Ostavite prazno za neograničenu pretplatu.', + 'submit_create' => 'Napravi pretplatu', + 'submit_edit' => 'Uredi pretplatu', + ], + 'form_link_add' => [ + 'link' => 'Veza strane za pretplate', + 'link_hint' => 'Ovo će dodati poziv na akciju na veb lokaciji koji poziva slušaoce da se pretplate na podkast.', + 'submit' => 'Sačuvaj vezu', + ], + 'suspend_form' => [ + 'disclaimer' => 'Obustavljanje pretplate će ograničiti pretplatniku pristup premijum sadržaju. I dalje ćete moći da uklonite obustavu nakon toga.', + 'reason' => 'Razlog', + 'reason_placeholder' => 'Zašto ukidate pretplatu?', + "submit" => 'Ukini pretplatu', + ], + 'delete_form' => [ + 'disclaimer' => 'Brisanje {subscriber} pretplate će ukloniti svu analitiku vezanu za tog pretplatnika.', + 'understand' => 'Razumem, želim da trajno ukinem pretplatu', + 'submit' => 'Ukloni pretplatu', + ], + 'messages' => [ + 'addSuccess' => 'Nova pretplata dodata! Poruka dobrodođlice je poslata {subscriber} putem E-pošte.', + 'addError' => 'Nije moguće dodati pretplatu.', + 'editSuccess' => 'Datum isteka pretplate je ažuriran! Poruka je poslata {subscriber} putem E-pošte.', + 'editError' => 'Nije moguće urediti pretplatu.', + 'regenerateTokenSuccess' => 'Token regenerisan! Novi token je poslat {subscriber} putem E-pošte.', + 'regenerateTokenError' => 'Token nije moguće regenerisati.', + 'deleteSuccess' => 'Pretplata je uklonjena! Poruka je poslata {subscriber} putem E-pošte.', + 'deleteError' => 'Nije moguće ukloniti pretplatu.', + 'suspendSuccess' => 'Pretplata je ukinuta! Poruka je poslata {subscriber} putem E-pošte.', + 'suspendError' => 'Nije moguće prekinuti pretplatu.', + 'resumeSuccess' => 'Pretplana je obnovljena! Poruka je poslata {subscriber} putem E-pošte.', + 'resumeError' => 'Nije moguće obnoviti pretplatu.', + 'linkSaveSuccess' => 'Veza za pretplatu je uspešno sačuvana! Pojaviće se na Veb strani kao poziv na akciju!', + 'linkRemoveSuccess' => 'Veza za pretplatu je uspešno uklonjena!', + ], + 'emails' => [ + 'greeting' => 'Hej,', + 'token' => 'Vaš token: {0}', + 'unique_feed_link' => 'Vaša jedinstvena veza sa fidom: {0}', + 'how_to_use' => 'Kako se koristi?', + 'two_ways' => 'Imate dva načina kako možete otključati premijum epizode:', + 'import_into_app' => 'Kopirajte svoj jedinstveni url fid u svoju omiljenu aplikaciju za podkaste (uvezite ga kao privatni fid da biste sprečili otkrivanje vaših akreditiva).', + 'go_to_website' => 'Idite na {podcastWebsite} veb stranicu i otključajte podkast koristeći vaš token.', + 'welcome_subject' => 'Dobrodošli na {podcastTitle}', + 'welcome' => 'Pretplatili ste se na {podcastTitle}, hvala vam i dobrodošli!', + 'welcome_token_title' => 'Evo vaših akreditiva kojima otključavate premijum epizode ovog podkasta:', + 'welcome_expires' => 'Vaša pretplata ističe {0}.', + 'welcome_never_expires' => 'Vaša pretplata je podešena tako da ne može da istekne.', + 'reset_subject' => 'Vaš token je resetovan!', + 'reset_token' => 'Vaš pristup {podcastTitle} je resetovan!', + 'reset_token_title' => 'Nove akreditive su kreirane kako bi ste pristupili premijum epizodama podkasta:', + 'edited_subject' => 'Vaša pretplata je ažurirana!', + 'edited_expires' => 'Vaša pretplata na {podcastTitle} ističe {expiresAt}.', + 'edited_never_expires' => 'Vaša pretplata na {podcastTitle} je podešena tako da nikad ne istekne!', + 'suspended_subject' => 'Vaša pretplata je ukinuta!', + 'suspended' => 'Vaša pretplata na {podcastTitle} je ukinuta! Više ne možete pristupiti premijum epizodama ovog podkasta.', + 'suspended_reason' => 'Razlog: {0}', + 'resumed_subject' => 'Vaša pretplata je ponovo pokrenuta!', + 'resumed' => 'Vaša pretplata na {podcastTitle} je ponovo pokrenuta! Sada ponovo možete pristupiti premijum epizodama ovog podkasta.', + 'deleted_subject' => 'Vaša pretplata je uklonjena!', + 'deleted' => 'Vaša pretplata na {podcastTitle} je uklonjena! Vipe ne možete pristupiti premijum epizodama ovog podkasta.', + 'footer' => '{castopod} hostovan na {host}', + ], +]; diff --git a/modules/PremiumPodcasts/Language/sv/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/sv/PremiumPodcasts.php index 18c0dd4e..21dcd34b 100644 --- a/modules/PremiumPodcasts/Language/sv/PremiumPodcasts.php +++ b/modules/PremiumPodcasts/Language/sv/PremiumPodcasts.php @@ -9,26 +9,26 @@ declare(strict_types=1); */ return [ - 'podcast_is_premium' => 'Podcast contains premium episodes', - 'episode_is_premium' => 'Episode is premium, only available to premium subscribers', - 'unlock_episode' => 'This episode is for premium subscribers only. Click to unlock it!', - 'banner_unlock' => 'This podcast contains premium episodes, only available to premium subscribers.', - 'banner_lock' => 'Podcast is unlocked, enjoy the premium episodes!', - 'subscribe' => 'Subscribe', - 'lock' => 'Lock', - 'unlock' => 'Unlock', + 'podcast_is_premium' => 'Podcast innehåller premium avsnitt', + 'episode_is_premium' => 'Avsnitt är premium, endast tillgängligt för premium-prenumeranter', + 'unlock_episode' => 'Denna episod är endast för premiumprenumeranter. Klicka för att låsa upp den!', + 'banner_unlock' => 'Denna podcast innehåller premiumavsnitt som endast är tillgängliga för premiumprenumeranter.', + 'banner_lock' => 'Podcast är olåst, njut av premiumavsnitt!', + 'subscribe' => 'Prenumerera', + 'lock' => 'Lås', + 'unlock' => 'Lås upp', 'unlock_form' => [ - 'title' => 'Premium content', - 'subtitle' => 'This podcast contains locked premium episodes! Do you have the key to unlock them?', - 'token' => 'Enter your key', - 'token_hint' => 'If you are subscribed to {podcastTitle}, you may copy the key that was sent to you via email and paste it here.', - 'submit' => 'Unlock all episodes!', - 'call_to_action' => 'Unlock all episodes of {podcastTitle}:', - 'subscribe_cta' => 'Subscribe now!', + 'title' => 'Premium-innehåll', + 'subtitle' => 'Denna podcast innehåller låsta premium-avsnitt! Har du nyckeln för att låsa upp dem?', + 'token' => 'Ange din nyckel', + 'token_hint' => 'Om du prenumererar på {podcastTitle} kan du kopiera nyckeln som skickades till dig via e-post och klistra in den här.', + 'submit' => 'Lås upp alla avsnitt!', + 'call_to_action' => 'Lås upp alla avsnitt av {podcastTitle}:', + 'subscribe_cta' => 'Prenumerera nu!', ], 'messages' => [ - 'unlockSuccess' => 'Podcast was successfully unlocked! Enjoy the premium episodes!', - 'unlockBadAttempt' => 'Your key does not seem to be working…', - 'lockSuccess' => 'Podcast was successfully locked!', + 'unlockSuccess' => 'Podcast har låsts upp! Njut av premiumavsnitt!', + 'unlockBadAttempt' => 'Din nyckel verkar inte fungera…', + 'lockSuccess' => 'Podcast har låsts!', ], ]; diff --git a/modules/PremiumPodcasts/Language/sv/Subscription.php b/modules/PremiumPodcasts/Language/sv/Subscription.php index f8af256f..06caf6cc 100644 --- a/modules/PremiumPodcasts/Language/sv/Subscription.php +++ b/modules/PremiumPodcasts/Language/sv/Subscription.php @@ -9,92 +9,92 @@ declare(strict_types=1); */ return [ - 'podcast_subscriptions' => 'Podcast subscriptions', - 'add' => 'New subscription', - 'view' => 'View subscription', - 'edit' => 'Edit subscription', - 'regenerate_token' => 'Regenerate token', - 'suspend' => 'Suspend subscription', - 'resume' => 'Resume subscription', - 'delete' => 'Delete subscription', + 'podcast_subscriptions' => 'Podcast prenumerationer', + 'add' => 'Ny prenumeration', + 'view' => 'Visa prenumeration', + 'edit' => 'Ändra prenumeration', + 'regenerate_token' => 'Generera om token', + 'suspend' => 'Avaktivera prenumeration', + 'resume' => 'Återuppta prenumeration', + 'delete' => 'Radera prenumeration', 'status' => [ - 'active' => 'Active', - 'suspended' => 'Suspended', - 'expired' => 'Expired', + 'active' => 'Aktiv', + 'suspended' => 'Suspenderad', + 'expired' => 'Utgått', ], 'list' => [ - 'number' => 'Number', - 'email' => 'Email', - 'expiration_date' => 'Expiration date', - 'unlimited' => 'Unlimited', - 'downloads' => 'Downloads', + 'number' => 'Nummer', + 'email' => 'Epost', + 'expiration_date' => 'Utgångsdatum', + 'unlimited' => 'Obegränsat', + 'downloads' => 'Nerladdningar', 'status' => 'Status', ], 'form' => [ - 'email' => 'Email', - 'expiration_date' => 'Expiration date', - 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', - 'submit_add' => 'Add subscription', - 'submit_edit' => 'Edit subscription', + 'email' => 'Epost', + 'expiration_date' => 'Utgångsdatum', + 'expiration_date_hint' => 'Datum och tid då prenumerationen går ut. Lämna tomt för ett obegränsat abonnemang.', + 'submit_create' => 'Create subscription', + 'submit_edit' => 'Ändra prenumeration', ], 'form_link_add' => [ - 'link' => 'Subscription page link', - 'link_hint' => 'This will add a call to action in the website inviting listeners to subscribe to the podcast.', - 'submit' => 'Save link', + 'link' => 'Länk för prenumerationssida', + 'link_hint' => 'Detta kommer att lägga till en uppmaning till åtgärder på webbplatsen som bjuder in lyssnare att prenumerera på podcasten.', + 'submit' => 'Spara länk', ], 'suspend_form' => [ - 'disclaimer' => 'Suspending the subscription will restrict the subscriber from having access to the premium content. You will still be able to lift the suspension afterwards.', - 'reason' => 'Reason', - 'reason_placeholder' => 'Why are you suspending the subscription?', - "submit" => 'Suspend subscription', + 'disclaimer' => 'Avstängning av abonnemanget kommer att begränsa abonnenten från att ha tillgång till premiuminnehållet. Du kommer fortfarande att kunna lyfta suspensionen efteråt.', + 'reason' => 'Orsak', + 'reason_placeholder' => 'Varför stänger du av prenumerationen?', + "submit" => 'Avaktivera prenumeration', ], 'delete_form' => [ - 'disclaimer' => 'Deleting {subscriber}\'s subscription will remove all analytics data associated with it.', - 'understand' => 'I understand, remove the subscription permanently', - 'submit' => 'Remove subscription', + 'disclaimer' => 'Borttagning av {subscriber}s prenumeration kommer att ta bort all analysdata som är kopplad till den.', + 'understand' => 'Jag förstår, ta bort prenumerationen permanent', + 'submit' => 'Ta bort prenumeration', ], 'messages' => [ - 'addSuccess' => 'New subscription added! A welcome email was sent to {subscriber}.', - 'addError' => 'Subscription could not be added.', - 'editSuccess' => 'Subscription expiry date was updated! An email was sent to {subscriber}.', - 'editError' => 'Subscription could not be edited.', - 'regenerateTokenSuccess' => 'Token regenerated! An email was sent to {subscriber} with the new token.', - 'regenerateTokenError' => 'Token could not be regenerated.', - 'deleteSuccess' => 'Subscription was removed! An email was sent to {subscriber}.', - 'deleteError' => 'Subscription could not be removed.', - 'suspendSuccess' => 'Subscription was suspended! An email was sent to {subscriber}.', - 'suspendError' => 'Subscription could not be suspended.', - 'resumeSuccess' => 'Subscription was resumed! An email was sent to {subscriber}.', - 'resumeError' => 'Subscription could not be resumed.', - 'linkSaveSuccess' => 'Subscription link was saved successfully! It will appear in the website as a Call To Action!', - 'linkRemoveSuccess' => 'Subscription link was removed successfully!', + 'addSuccess' => 'Ny prenumeration tillagd! Ett välkomstmeddelande skickades till {subscriber}.', + 'addError' => 'Prenumerationen kunde inte läggas till.', + 'editSuccess' => 'Prenumeration utgångsdatum uppdaterades! Ett e-postmeddelande skickades till {subscriber}.', + 'editError' => 'Prenumerationen kunde inte redigeras.', + 'regenerateTokenSuccess' => 'Token regenererad! Ett e-postmeddelande skickades till {subscriber} med den nya token.', + 'regenerateTokenError' => 'Token kunde inte regenereras.', + 'deleteSuccess' => 'Prenumerationen har tagits bort! Ett e-postmeddelande har skickats till {subscriber}.', + 'deleteError' => 'Prenumerationen kunde inte tas bort.', + 'suspendSuccess' => 'Prenumerationen suspenderades! Ett e-postmeddelande skickades till {subscriber}.', + 'suspendError' => 'Prenumerationen kunde inte stängas av.', + 'resumeSuccess' => 'Prenumerationen återupptades! Ett e-postmeddelande skickades till {subscriber}.', + 'resumeError' => 'Prenumerationen kunde inte återupptas.', + 'linkSaveSuccess' => 'Prenumerationslänken har sparats! Den kommer att visas på webbplatsen som en Call To Action!', + 'linkRemoveSuccess' => 'Prenumerationslänken har tagits bort!', ], 'emails' => [ - 'greeting' => 'Hey,', - 'token' => 'Your token: {0}', - 'unique_feed_link' => 'Your unique feed link: {0}', - 'how_to_use' => 'How to use?', - 'two_ways' => 'You have two ways of unlocking the premium episodes:', - 'import_into_app' => 'Copy your unique feed url inside your favourite podcast app (import it as a private feed to prevent exposing your credentials).', - 'go_to_website' => 'Go to {podcastWebsite}\'s website and unlock the podcast with your token.', - 'welcome_subject' => 'Welcome to {podcastTitle}', - 'welcome' => 'You have subscribed to {podcastTitle}, thank you and welcome aboard!', - 'welcome_token_title' => 'Here are your credentials to unlock the podcast\'s premium episodes:', - 'welcome_expires' => 'Your subscription was set to expire on {0}.', - 'welcome_never_expires' => 'Your subscription was set to never expire.', - 'reset_subject' => 'Your token was reset!', - 'reset_token' => 'Your access to {podcastTitle} has been reset!', - 'reset_token_title' => 'New credentials have been generated for you to unlock the podcast\'s premium episodes:', - 'edited_subject' => 'Your subscription has been updated!', - 'edited_expires' => 'Your subscription for {podcastTitle} was set to expire on {expiresAt}.', - 'edited_never_expires' => 'Your subscription for {podcastTitle} was set to never expire!', - 'suspended_subject' => 'Your subscription has been suspended!', - 'suspended' => 'Your subscription for {podcastTitle} has been suspended! You can no longer access the podcast\'s premium episodes.', - 'suspended_reason' => 'That is for the following reason: {0}', - 'resumed_subject' => 'Your subscription has been resumed!', - 'resumed' => 'Your subscription for {podcastTitle} has been resumed! You may access the podcast\'s premium episodes again.', - 'deleted_subject' => 'Your subscription has been removed!', - 'deleted' => 'Your subscription for {podcastTitle} has been removed! You no longer have access to the podcast\'s premium episodes.', - 'footer' => '{castopod} hosted on {host}', + 'greeting' => 'Hej,', + 'token' => 'Din token: {0}', + 'unique_feed_link' => 'Din unika flödeslänk: {0}', + 'how_to_use' => 'Hur gör man?', + 'two_ways' => 'Du har två sätt att låsa upp premiumavsnitt:', + 'import_into_app' => 'Kopiera din unika feed url inuti din favorit podcast-app (importera den som ett privat flöde för att förhindra att du exponerar dina referenser).', + 'go_to_website' => 'Gå till {podcastWebsite}s webbplats och lås upp podcasten med din token.', + 'welcome_subject' => 'Välkommen till {podcastTitle}', + 'welcome' => 'Du har prenumererat på {podcastTitle}, tack och välkommen ombord!', + 'welcome_token_title' => 'Här är dina referenser för att låsa upp podcastens premiumavsnitt:', + 'welcome_expires' => 'Ditt abonnemang var inställt på att löpa ut {0}.', + 'welcome_never_expires' => 'Din prenumeration var inställd på att aldrig upphöra.', + 'reset_subject' => 'Din token återställdes!', + 'reset_token' => 'Din åtkomst till {podcastTitle} har återställts!', + 'reset_token_title' => 'Nya autentiseringsuppgifter har skapats för att du ska kunna låsa upp podcastens premiumavsnitt:', + 'edited_subject' => 'Din prenumeration har uppdaterats!', + 'edited_expires' => 'Ditt abonnemang för {podcastTitle} sattes att löpa ut den {expiresAt}.', + 'edited_never_expires' => 'Din prenumeration på {podcastTitle} har ställts in på att aldrig upphör!', + 'suspended_subject' => 'Din prenumeration har stängts av!', + 'suspended' => 'Din prenumeration på {podcastTitle} har stängts av! Du kan inte längre komma åt podcastens premiumavsnitt.', + 'suspended_reason' => 'Det är av följande skäl: {0}', + 'resumed_subject' => 'Din prenumeration har återupptagits!', + 'resumed' => 'Din prenumeration på {podcastTitle} har återupptagits! Du kan komma åt podcastens premiumavsnitt igen.', + 'deleted_subject' => 'Din prenumeration har tagits bort!', + 'deleted' => 'Din prenumeration på {podcastTitle} har tagits bort! Du har inte längre tillgång till podcastens premiumavsnitt.', + 'footer' => '{castopod} hostas på {host}', ], ]; diff --git a/modules/PremiumPodcasts/Language/uk/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/uk/PremiumPodcasts.php new file mode 100644 index 00000000..bd87ddae --- /dev/null +++ b/modules/PremiumPodcasts/Language/uk/PremiumPodcasts.php @@ -0,0 +1,34 @@ + 'Подкаст містить преміум-епізоди', + 'episode_is_premium' => 'Цей подкаст містить преміум епізоди, тільки для преміум підписників', + 'unlock_episode' => 'Цей епізод призначений тільки для преміум підписників. Натисніть, щоб розблокувати його!', + 'banner_unlock' => 'Цей подкаст містить преміум епізоди, тільки для преміум підписників.', + 'banner_lock' => 'Подкаст був успішно розблокований! Насолоджуйтесь преміум епізодами!', + 'subscribe' => 'Підписатися', + 'lock' => 'Заблокувати', + 'unlock' => 'Розблокувати', + 'unlock_form' => [ + 'title' => 'Преміум контент', + 'subtitle' => 'Цей подкаст містить заблоковані преміум епізоди! Ви маєте ключ для їх розблокування?', + 'token' => 'Введіть ваш ключ', + 'token_hint' => 'Якщо ви підписані на {podcastTitle}, ви можете скопіювати ключ, відправлений вам на електрону пошту та вставити його тут.', + 'submit' => 'Розблокувати всі серії!', + 'call_to_action' => 'Розблокувати всі серії з {podcastTitle}:', + 'subscribe_cta' => 'Підписатися зараз!', + ], + 'messages' => [ + 'unlockSuccess' => 'Подкаст був успішно розблокований! Насолоджуйтесь преміум епізодами!', + 'unlockBadAttempt' => 'Здається, ваш ключ не працює…', + 'lockSuccess' => 'Подкаст успішно заблокований!', + ], +]; diff --git a/modules/PremiumPodcasts/Language/uk/Subscription.php b/modules/PremiumPodcasts/Language/uk/Subscription.php new file mode 100644 index 00000000..e83f0cb2 --- /dev/null +++ b/modules/PremiumPodcasts/Language/uk/Subscription.php @@ -0,0 +1,100 @@ + 'Podcast subscriptions', + 'add' => 'New subscription', + 'view' => 'View subscription', + 'edit' => 'Edit subscription', + 'regenerate_token' => 'Regenerate token', + 'suspend' => 'Suspend subscription', + 'resume' => 'Resume subscription', + 'delete' => 'Delete subscription', + 'status' => [ + 'active' => 'Active', + 'suspended' => 'Suspended', + 'expired' => 'Expired', + ], + 'list' => [ + 'number' => 'Number', + 'email' => 'Email', + 'expiration_date' => 'Expiration date', + 'unlimited' => 'Unlimited', + 'downloads' => 'Downloads', + 'status' => 'Status', + ], + 'form' => [ + 'email' => 'Email', + 'expiration_date' => 'Expiration date', + 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', + 'submit_create' => 'Create subscription', + 'submit_edit' => 'Edit subscription', + ], + 'form_link_add' => [ + 'link' => 'Subscription page link', + 'link_hint' => 'This will add a call to action in the website inviting listeners to subscribe to the podcast.', + 'submit' => 'Save link', + ], + 'suspend_form' => [ + 'disclaimer' => 'Suspending the subscription will restrict the subscriber from having access to the premium content. You will still be able to lift the suspension afterwards.', + 'reason' => 'Reason', + 'reason_placeholder' => 'Why are you suspending the subscription?', + "submit" => 'Suspend subscription', + ], + 'delete_form' => [ + 'disclaimer' => 'Deleting {subscriber}\'s subscription will remove all analytics data associated with it.', + 'understand' => 'I understand, remove the subscription permanently', + 'submit' => 'Remove subscription', + ], + 'messages' => [ + 'addSuccess' => 'New subscription added! A welcome email was sent to {subscriber}.', + 'addError' => 'Subscription could not be added.', + 'editSuccess' => 'Subscription expiry date was updated! An email was sent to {subscriber}.', + 'editError' => 'Subscription could not be edited.', + 'regenerateTokenSuccess' => 'Token regenerated! An email was sent to {subscriber} with the new token.', + 'regenerateTokenError' => 'Token could not be regenerated.', + 'deleteSuccess' => 'Subscription was removed! An email was sent to {subscriber}.', + 'deleteError' => 'Subscription could not be removed.', + 'suspendSuccess' => 'Subscription was suspended! An email was sent to {subscriber}.', + 'suspendError' => 'Subscription could not be suspended.', + 'resumeSuccess' => 'Subscription was resumed! An email was sent to {subscriber}.', + 'resumeError' => 'Subscription could not be resumed.', + 'linkSaveSuccess' => 'Subscription link was saved successfully! It will appear in the website as a Call To Action!', + 'linkRemoveSuccess' => 'Subscription link was removed successfully!', + ], + 'emails' => [ + 'greeting' => 'Hey,', + 'token' => 'Your token: {0}', + 'unique_feed_link' => 'Your unique feed link: {0}', + 'how_to_use' => 'How to use?', + 'two_ways' => 'You have two ways of unlocking the premium episodes:', + 'import_into_app' => 'Copy your unique feed url inside your favourite podcast app (import it as a private feed to prevent exposing your credentials).', + 'go_to_website' => 'Go to {podcastWebsite}\'s website and unlock the podcast with your token.', + 'welcome_subject' => 'Welcome to {podcastTitle}', + 'welcome' => 'You have subscribed to {podcastTitle}, thank you and welcome aboard!', + 'welcome_token_title' => 'Here are your credentials to unlock the podcast\'s premium episodes:', + 'welcome_expires' => 'Your subscription was set to expire on {0}.', + 'welcome_never_expires' => 'Your subscription was set to never expire.', + 'reset_subject' => 'Your token was reset!', + 'reset_token' => 'Your access to {podcastTitle} has been reset!', + 'reset_token_title' => 'New credentials have been generated for you to unlock the podcast\'s premium episodes:', + 'edited_subject' => 'Your subscription has been updated!', + 'edited_expires' => 'Your subscription for {podcastTitle} was set to expire on {expiresAt}.', + 'edited_never_expires' => 'Your subscription for {podcastTitle} was set to never expire!', + 'suspended_subject' => 'Your subscription has been suspended!', + 'suspended' => 'Your subscription for {podcastTitle} has been suspended! You can no longer access the podcast\'s premium episodes.', + 'suspended_reason' => 'That is for the following reason: {0}', + 'resumed_subject' => 'Your subscription has been resumed!', + 'resumed' => 'Your subscription for {podcastTitle} has been resumed! You may access the podcast\'s premium episodes again.', + 'deleted_subject' => 'Your subscription has been removed!', + 'deleted' => 'Your subscription for {podcastTitle} has been removed! You no longer have access to the podcast\'s premium episodes.', + 'footer' => '{castopod} hosted on {host}', + ], +]; diff --git a/modules/PremiumPodcasts/Language/zh-Hans/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/zh-hans/PremiumPodcasts.php similarity index 100% rename from modules/PremiumPodcasts/Language/zh-Hans/PremiumPodcasts.php rename to modules/PremiumPodcasts/Language/zh-hans/PremiumPodcasts.php diff --git a/modules/PremiumPodcasts/Language/zh-Hans/Subscription.php b/modules/PremiumPodcasts/Language/zh-hans/Subscription.php similarity index 95% rename from modules/PremiumPodcasts/Language/zh-Hans/Subscription.php rename to modules/PremiumPodcasts/Language/zh-hans/Subscription.php index ceaeb7df..7b8e3d5b 100644 --- a/modules/PremiumPodcasts/Language/zh-Hans/Subscription.php +++ b/modules/PremiumPodcasts/Language/zh-hans/Subscription.php @@ -34,7 +34,7 @@ return [ 'email' => '邮箱', 'expiration_date' => '到期日', 'expiration_date_hint' => '订阅到期的日期和时间。 留空时没有订阅限制。', - 'submit_add' => '添加订阅', + 'submit_create' => 'Create subscription', 'submit_edit' => '编辑订阅', ], 'form_link_add' => [ @@ -72,10 +72,10 @@ return [ 'emails' => [ 'greeting' => '嘿,', 'token' => '你的令牌: {0}', - 'unique_feed_link' => '你唯一的源链接:{0}', + 'unique_feed_link' => '你唯一的摘要链接:{0}', 'how_to_use' => '如何使用?', 'two_ways' => '你有两种解锁高级剧集的方法:', - 'import_into_app' => '在你最喜欢的播客应用程序中复制你唯一的源 URL(将其作为私人源导入以防止暴露你的凭据)。', + 'import_into_app' => '在你最喜欢的播客应用程序中复制你唯一的摘要 URL(将其作为私人源导入以防止暴露你的凭据)。', 'go_to_website' => '访问 {podcastWebsite} 的网站并使用你的令牌解锁播客。', 'welcome_subject' => '欢迎来到 {podcastTitle}', 'welcome' => '你已订阅 {podcastTitle},谢谢,欢迎加入!', diff --git a/modules/PremiumPodcasts/Language/zh-hant/PremiumPodcasts.php b/modules/PremiumPodcasts/Language/zh-hant/PremiumPodcasts.php new file mode 100644 index 00000000..18c0dd4e --- /dev/null +++ b/modules/PremiumPodcasts/Language/zh-hant/PremiumPodcasts.php @@ -0,0 +1,34 @@ + 'Podcast contains premium episodes', + 'episode_is_premium' => 'Episode is premium, only available to premium subscribers', + 'unlock_episode' => 'This episode is for premium subscribers only. Click to unlock it!', + 'banner_unlock' => 'This podcast contains premium episodes, only available to premium subscribers.', + 'banner_lock' => 'Podcast is unlocked, enjoy the premium episodes!', + 'subscribe' => 'Subscribe', + 'lock' => 'Lock', + 'unlock' => 'Unlock', + 'unlock_form' => [ + 'title' => 'Premium content', + 'subtitle' => 'This podcast contains locked premium episodes! Do you have the key to unlock them?', + 'token' => 'Enter your key', + 'token_hint' => 'If you are subscribed to {podcastTitle}, you may copy the key that was sent to you via email and paste it here.', + 'submit' => 'Unlock all episodes!', + 'call_to_action' => 'Unlock all episodes of {podcastTitle}:', + 'subscribe_cta' => 'Subscribe now!', + ], + 'messages' => [ + 'unlockSuccess' => 'Podcast was successfully unlocked! Enjoy the premium episodes!', + 'unlockBadAttempt' => 'Your key does not seem to be working…', + 'lockSuccess' => 'Podcast was successfully locked!', + ], +]; diff --git a/modules/PremiumPodcasts/Language/zh-hant/Subscription.php b/modules/PremiumPodcasts/Language/zh-hant/Subscription.php new file mode 100644 index 00000000..e83f0cb2 --- /dev/null +++ b/modules/PremiumPodcasts/Language/zh-hant/Subscription.php @@ -0,0 +1,100 @@ + 'Podcast subscriptions', + 'add' => 'New subscription', + 'view' => 'View subscription', + 'edit' => 'Edit subscription', + 'regenerate_token' => 'Regenerate token', + 'suspend' => 'Suspend subscription', + 'resume' => 'Resume subscription', + 'delete' => 'Delete subscription', + 'status' => [ + 'active' => 'Active', + 'suspended' => 'Suspended', + 'expired' => 'Expired', + ], + 'list' => [ + 'number' => 'Number', + 'email' => 'Email', + 'expiration_date' => 'Expiration date', + 'unlimited' => 'Unlimited', + 'downloads' => 'Downloads', + 'status' => 'Status', + ], + 'form' => [ + 'email' => 'Email', + 'expiration_date' => 'Expiration date', + 'expiration_date_hint' => 'The date and time at which the subscription expires. Leave empty for an unlimited subscription.', + 'submit_create' => 'Create subscription', + 'submit_edit' => 'Edit subscription', + ], + 'form_link_add' => [ + 'link' => 'Subscription page link', + 'link_hint' => 'This will add a call to action in the website inviting listeners to subscribe to the podcast.', + 'submit' => 'Save link', + ], + 'suspend_form' => [ + 'disclaimer' => 'Suspending the subscription will restrict the subscriber from having access to the premium content. You will still be able to lift the suspension afterwards.', + 'reason' => 'Reason', + 'reason_placeholder' => 'Why are you suspending the subscription?', + "submit" => 'Suspend subscription', + ], + 'delete_form' => [ + 'disclaimer' => 'Deleting {subscriber}\'s subscription will remove all analytics data associated with it.', + 'understand' => 'I understand, remove the subscription permanently', + 'submit' => 'Remove subscription', + ], + 'messages' => [ + 'addSuccess' => 'New subscription added! A welcome email was sent to {subscriber}.', + 'addError' => 'Subscription could not be added.', + 'editSuccess' => 'Subscription expiry date was updated! An email was sent to {subscriber}.', + 'editError' => 'Subscription could not be edited.', + 'regenerateTokenSuccess' => 'Token regenerated! An email was sent to {subscriber} with the new token.', + 'regenerateTokenError' => 'Token could not be regenerated.', + 'deleteSuccess' => 'Subscription was removed! An email was sent to {subscriber}.', + 'deleteError' => 'Subscription could not be removed.', + 'suspendSuccess' => 'Subscription was suspended! An email was sent to {subscriber}.', + 'suspendError' => 'Subscription could not be suspended.', + 'resumeSuccess' => 'Subscription was resumed! An email was sent to {subscriber}.', + 'resumeError' => 'Subscription could not be resumed.', + 'linkSaveSuccess' => 'Subscription link was saved successfully! It will appear in the website as a Call To Action!', + 'linkRemoveSuccess' => 'Subscription link was removed successfully!', + ], + 'emails' => [ + 'greeting' => 'Hey,', + 'token' => 'Your token: {0}', + 'unique_feed_link' => 'Your unique feed link: {0}', + 'how_to_use' => 'How to use?', + 'two_ways' => 'You have two ways of unlocking the premium episodes:', + 'import_into_app' => 'Copy your unique feed url inside your favourite podcast app (import it as a private feed to prevent exposing your credentials).', + 'go_to_website' => 'Go to {podcastWebsite}\'s website and unlock the podcast with your token.', + 'welcome_subject' => 'Welcome to {podcastTitle}', + 'welcome' => 'You have subscribed to {podcastTitle}, thank you and welcome aboard!', + 'welcome_token_title' => 'Here are your credentials to unlock the podcast\'s premium episodes:', + 'welcome_expires' => 'Your subscription was set to expire on {0}.', + 'welcome_never_expires' => 'Your subscription was set to never expire.', + 'reset_subject' => 'Your token was reset!', + 'reset_token' => 'Your access to {podcastTitle} has been reset!', + 'reset_token_title' => 'New credentials have been generated for you to unlock the podcast\'s premium episodes:', + 'edited_subject' => 'Your subscription has been updated!', + 'edited_expires' => 'Your subscription for {podcastTitle} was set to expire on {expiresAt}.', + 'edited_never_expires' => 'Your subscription for {podcastTitle} was set to never expire!', + 'suspended_subject' => 'Your subscription has been suspended!', + 'suspended' => 'Your subscription for {podcastTitle} has been suspended! You can no longer access the podcast\'s premium episodes.', + 'suspended_reason' => 'That is for the following reason: {0}', + 'resumed_subject' => 'Your subscription has been resumed!', + 'resumed' => 'Your subscription for {podcastTitle} has been resumed! You may access the podcast\'s premium episodes again.', + 'deleted_subject' => 'Your subscription has been removed!', + 'deleted' => 'Your subscription for {podcastTitle} has been removed! You no longer have access to the podcast\'s premium episodes.', + 'footer' => '{castopod} hosted on {host}', + ], +]; diff --git a/modules/PremiumPodcasts/Models/SubscriptionModel.php b/modules/PremiumPodcasts/Models/SubscriptionModel.php index 5f79271a..32619b19 100644 --- a/modules/PremiumPodcasts/Models/SubscriptionModel.php +++ b/modules/PremiumPodcasts/Models/SubscriptionModel.php @@ -28,7 +28,7 @@ class SubscriptionModel extends Model protected $primaryKey = 'id'; /** - * @var string[] + * @var list */ protected $allowedFields = [ 'id', @@ -42,9 +42,6 @@ class SubscriptionModel extends Model 'updated_by', ]; - /** - * @noRector - */ protected $returnType = Subscription::class; /** @@ -58,12 +55,17 @@ class SubscriptionModel extends Model protected $useTimestamps = true; /** - * @var string[] + * @var list + */ + protected $afterInsert = ['clearCache']; + + /** + * @var list */ protected $afterUpdate = ['clearCache']; /** - * @var string[] + * @var list */ protected $beforeDelete = ['clearCache']; @@ -114,11 +116,13 @@ class SubscriptionModel extends Model return $subscriptionModel ->where([ - 'token' => hash('sha256', $token), + 'token' => hash('sha256', $token), 'status' => 'active', - 'expires_at' => null, ]) + ->groupStart() + ->where('expires_at') ->orWhere('`expires_at` > UTC_TIMESTAMP()', null, false) + ->groupEnd() ->first(); } @@ -129,7 +133,13 @@ class SubscriptionModel extends Model */ protected function clearCache(array $data): array { - $subscription = (new self())->find(is_array($data['id']) ? $data['id'][0] : $data['id']); + /** @var ?Subscription */ + $subscription = new self() + ->find(is_array($data['id']) ? $data['id'][0] : $data['id']); + + if (! $subscription instanceof Subscription) { + return $data; + } cache() ->delete("subscription#{$subscription->id}"); diff --git a/modules/PremiumPodcasts/PremiumPodcasts.php b/modules/PremiumPodcasts/PremiumPodcasts.php index e1469d97..036b6f05 100644 --- a/modules/PremiumPodcasts/PremiumPodcasts.php +++ b/modules/PremiumPodcasts/PremiumPodcasts.php @@ -63,7 +63,7 @@ class PremiumPodcasts { if (array_key_exists( $podcastHandle, - $this->subscriptions + $this->subscriptions, ) && ($this->subscriptions[$podcastHandle] instanceof Subscription)) { return true; } @@ -86,7 +86,7 @@ class PremiumPodcasts // Store the current subscription object $this->subscriptions[$podcastHandle] = $this->subscriptionModel->getSubscriptionById( - $this->subscriptions[$podcastHandle]->id + $this->subscriptions[$podcastHandle]->id, ); if (! $this->subscriptions[$podcastHandle] instanceof Subscription) { diff --git a/modules/Update/Commands/DatabaseUpdate.php b/modules/Update/Commands/DatabaseUpdate.php new file mode 100644 index 00000000..380f5305 --- /dev/null +++ b/modules/Update/Commands/DatabaseUpdate.php @@ -0,0 +1,35 @@ +setNamespace(null) + ->latest(); + } +} diff --git a/modules/WebSub/Controllers/WebSubController.php b/modules/WebSub/Commands/Publish.php similarity index 64% rename from modules/WebSub/Controllers/WebSubController.php rename to modules/WebSub/Commands/Publish.php index 11a0a2d7..c0f47b22 100644 --- a/modules/WebSub/Controllers/WebSubController.php +++ b/modules/WebSub/Commands/Publish.php @@ -2,53 +2,57 @@ declare(strict_types=1); -/** - * @copyright 2022 Ad Aures - * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 - * @link https://castopod.org/ - */ - -namespace Modules\WebSub\Controllers; +namespace Modules\WebSub\Commands; use App\Models\EpisodeModel; use App\Models\PodcastModel; -use CodeIgniter\Controller; -use Config\Services; +use CodeIgniter\CLI\BaseCommand; +use CodeIgniter\HTTP\CURLRequest; use Exception; +use Override; -class WebSubController extends Controller +class Publish extends BaseCommand { - public function publish(): void + protected $group = 'Websub'; + + protected $name = 'websub:publish'; + + protected $description = 'Publishes feed updates to websub hubs.'; + + #[Override] + public function run(array $params): void { - /** @noRector RemoveAlwaysTrueIfConditionRector */ if (ENVIRONMENT !== 'production') { return; } + helper('misc'); + // get all podcasts that haven't been published yet // or having a published episode that hasn't been pushed yet $podcastModel = new PodcastModel(); - $podcasts = $podcastModel - ->distinct() + $podcastModel->builder() ->select('podcasts.*') + ->distinct() ->join('episodes', 'podcasts.id = episodes.podcast_id', 'left outer') ->where('podcasts.is_published_on_hubs', false) ->where('`' . $podcastModel->db->getPrefix() . 'podcasts`.`published_at` <= UTC_TIMESTAMP()', null, false) ->orGroupStart() ->where('episodes.is_published_on_hubs', false) ->where('`' . $podcastModel->db->getPrefix() . 'episodes`.`published_at` <= UTC_TIMESTAMP()', null, false) - ->groupEnd() - ->findAll(); + ->groupEnd(); + $podcasts = $podcastModel->findAll(); if ($podcasts === []) { return; } - $request = Services::curlrequest(); + /** @var CURLRequest $request */ + $request = service('curlrequest'); $requestOptions = [ 'headers' => [ - 'User-Agent' => 'Castopod/' . CP_VERSION . '; +' . base_url('', 'https'), + 'User-Agent' => 'Castopod/' . CP_VERSION . '; +' . base_url('', 'https'), 'Content-Type' => 'application/x-www-form-urlencoded', ], ]; @@ -59,7 +63,7 @@ class WebSubController extends Controller foreach ($podcasts as $podcast) { $requestOptions['form_params'] = [ 'hub.mode' => 'publish', - 'hub.url' => $podcast->feed_url, + 'hub.url' => $podcast->feed_url, ]; foreach ($hubUrls as $hub) { @@ -67,21 +71,23 @@ class WebSubController extends Controller $request->post($hub, $requestOptions); } catch (Exception $exception) { log_message( - 'critical', - "COULD NOT PUBLISH @{$podcast->handle} ON {$hub}" . PHP_EOL . $exception->getMessage() + 'warning', + "COULD NOT PUBLISH @{$podcast->handle} ON {$hub}" . PHP_EOL . $exception->getMessage(), ); } } // set podcast feed as having been pushed onto hubs - (new PodcastModel())->update($podcast->id, [ - 'is_published_on_hubs' => true, - ]); + new PodcastModel() + ->update($podcast->id, [ + 'is_published_on_hubs' => 1, + ]); // set newly published episodes as pushed onto hubs - (new EpisodeModel())->set('is_published_on_hubs', true) + new EpisodeModel() + ->set('is_published_on_hubs', true) ->where([ - 'podcast_id' => $podcast->id, + 'podcast_id' => $podcast->id, 'is_published_on_hubs' => false, ]) ->where('`published_at` <= UTC_TIMESTAMP()', null, false) diff --git a/modules/WebSub/Config/Routes.php b/modules/WebSub/Config/Routes.php deleted file mode 100644 index b86457ee..00000000 --- a/modules/WebSub/Config/Routes.php +++ /dev/null @@ -1,21 +0,0 @@ -group('', [ - 'namespace' => 'Modules\WebSub\Controllers', -], static function ($routes): void { - $routes->cli('scheduled-websub-publish', 'WebSubController::publish'); -}); diff --git a/modules/WebSub/Database/Migrations/2022-03-07-180000_add_is_published_on_hubs_to_podcasts.php b/modules/WebSub/Database/Migrations/2022-03-07-180000_add_is_published_on_hubs_to_podcasts.php index fb3d68ca..179e1bf9 100644 --- a/modules/WebSub/Database/Migrations/2022-03-07-180000_add_is_published_on_hubs_to_podcasts.php +++ b/modules/WebSub/Database/Migrations/2022-03-07-180000_add_is_published_on_hubs_to_podcasts.php @@ -10,22 +10,25 @@ declare(strict_types=1); namespace Modules\WebSub\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddIsPublishedOnHubsToPodcasts extends Migration +class AddIsPublishedOnHubsToPodcasts extends BaseMigration { + #[Override] public function up(): void { - $prefix = $this->db->getPrefix(); - - $createQuery = <<db->query($createQuery); + $this->forge->addColumn('podcasts', [ + 'is_published_on_hubs' => [ + 'type' => 'BOOLEAN', + 'null' => false, + 'default' => 0, + 'after' => 'custom_rss', + ], + ]); } + #[Override] public function down(): void { $this->forge->dropColumn('podcasts', 'is_published_on_hubs'); diff --git a/modules/WebSub/Database/Migrations/2022-03-07-181500_add_is_published_on_hubs_to_episodes.php b/modules/WebSub/Database/Migrations/2022-03-07-181500_add_is_published_on_hubs_to_episodes.php index dfe92a08..b57be027 100644 --- a/modules/WebSub/Database/Migrations/2022-03-07-181500_add_is_published_on_hubs_to_episodes.php +++ b/modules/WebSub/Database/Migrations/2022-03-07-181500_add_is_published_on_hubs_to_episodes.php @@ -10,22 +10,25 @@ declare(strict_types=1); namespace Modules\WebSub\Database\Migrations; -use CodeIgniter\Database\Migration; +use App\Database\Migrations\BaseMigration; +use Override; -class AddIsPublishedOnHubsToEpisodes extends Migration +class AddIsPublishedOnHubsToEpisodes extends BaseMigration { + #[Override] public function up(): void { - $prefix = $this->db->getPrefix(); - - $createQuery = <<db->query($createQuery); + $this->forge->addColumn('episodes', [ + 'is_published_on_hubs' => [ + 'type' => 'BOOLEAN', + 'null' => false, + 'default' => 0, + 'after' => 'custom_rss', + ], + ]); } + #[Override] public function down(): void { $this->forge->dropColumn('episodes', 'is_published_on_hubs'); diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 1831f5ab..00000000 --- a/package-lock.json +++ /dev/null @@ -1,28877 +0,0 @@ -{ - "name": "castopod-host", - "version": "1.0.0-beta.24", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "castopod-host", - "version": "1.0.0-beta.24", - "license": "AGPL-3.0-or-later", - "dependencies": { - "@amcharts/amcharts4": "^4.10.29", - "@amcharts/amcharts4-geodata": "^4.1.23", - "@codemirror/commands": "^6.1.2", - "@codemirror/lang-xml": "^6.0.0", - "@codemirror/language": "^6.2.1", - "@codemirror/state": "^6.1.2", - "@floating-ui/dom": "^1.0.2", - "@github/clipboard-copy-element": "^1.1.2", - "@github/hotkey": "^2.0.1", - "@github/markdown-toolbar-element": "^2.1.1", - "@github/time-elements": "^3.1.4", - "@tailwindcss/nesting": "^0.0.0-insiders.565cd3e", - "@vime/core": "^5.3.3", - "choices.js": "^10.1.0", - "codemirror": "^6.0.1", - "flatpickr": "^4.6.13", - "leaflet": "^1.9.2", - "leaflet.markercluster": "^1.5.3", - "lit": "^2.4.0", - "marked": "^4.1.1", - "wavesurfer.js": "^6.3.0", - "xml-formatter": "^2.6.1" - }, - "devDependencies": { - "@commitlint/cli": "^17.1.2", - "@commitlint/config-conventional": "^17.1.0", - "@semantic-release/changelog": "^6.0.1", - "@semantic-release/exec": "^6.0.3", - "@semantic-release/git": "^10.0.1", - "@semantic-release/gitlab": "^9.4.2", - "@tailwindcss/forms": "^0.5.3", - "@tailwindcss/line-clamp": "^0.4.2", - "@tailwindcss/typography": "^0.5.7", - "@types/leaflet": "^1.8.0", - "@types/marked": "^4.0.7", - "@types/wavesurfer.js": "^6.0.3", - "@typescript-eslint/eslint-plugin": "^5.40.0", - "@typescript-eslint/parser": "^5.40.0", - "all-contributors-cli": "^6.24.0", - "cross-env": "^7.0.3", - "cssnano": "^5.1.13", - "cz-conventional-changelog": "^3.3.0", - "eslint": "^8.25.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-prettier": "^4.2.1", - "husky": "^8.0.1", - "is-ci": "^3.0.1", - "lint-staged": "^13.0.3", - "postcss-import": "^15.0.0", - "postcss-nesting": "^10.2.0", - "postcss-preset-env": "^7.8.2", - "postcss-reporter": "^7.0.5", - "prettier": "2.7.1", - "prettier-plugin-organize-imports": "^3.1.1", - "semantic-release": "^19.0.5", - "stylelint": "^14.13.0", - "stylelint-config-standard": "^28.0.0", - "svgo": "^2.8.0", - "tailwindcss": "^3.1.8", - "typescript": "^4.8.4", - "vite": "2.8.6", - "vite-plugin-pwa": "^0.12.8", - "workbox-build": "^6.5.4", - "workbox-core": "^6.5.4", - "workbox-routing": "^6.5.4", - "workbox-strategies": "^6.5.4" - } - }, - "node_modules/@amcharts/amcharts4": { - "version": "4.10.29", - "resolved": "https://registry.npmjs.org/@amcharts/amcharts4/-/amcharts4-4.10.29.tgz", - "integrity": "sha512-uDCvm4V0Xs2jtI0Aa7XFH0jqoyEGx9I2ukFEwaaQzYq11vwowhJsgE3sSv2jsfKETYUvPXb32NbzrXZulE2ESg==", - "dependencies": { - "@babel/runtime": "^7.6.3", - "core-js": "^3.0.0", - "d3-force": "^3.0.0", - "d3-geo": "^3.0.1", - "d3-geo-projection": "^4.0.0", - "d3-selection": "^3.0.0", - "d3-transition": "^3.0.1", - "pdfmake": "^0.2.2", - "polylabel": "^1.0.2", - "raf": "^3.4.1", - "regression": "^2.0.1", - "rgbcolor": "^1.0.1", - "stackblur-canvas": "^2.0.0", - "tslib": "^2.0.1", - "xlsx": "^0.17.0" - } - }, - "node_modules/@amcharts/amcharts4-geodata": { - "version": "4.1.23", - "resolved": "https://registry.npmjs.org/@amcharts/amcharts4-geodata/-/amcharts4-geodata-4.1.23.tgz", - "integrity": "sha512-/hFqTuc2SUB0tAgygc8yj1xvTUdddltI7/15fAT9L5XHGERgF8qAa+AisQQ0UMITvrWIr5awJupWE3vDTlWsjQ==" - }, - "node_modules/@amcharts/amcharts4/node_modules/tslib": { - "version": "2.1.0", - "license": "0BSD" - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.6.tgz", - "integrity": "sha512-tzulrgDT0QD6U7BJ4TKVk2SDDg7wlP39P9yAx1RfLy7vP/7rsDRlWVfbWxElslu56+r7QOhB2NSDsabYYruoZQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.6.tgz", - "integrity": "sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.6", - "@babel/helper-compilation-targets": "^7.18.6", - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helpers": "^7.18.6", - "@babel/parser": "^7.18.6", - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.6", - "@babel/types": "^7.18.6", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.18.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.7.tgz", - "integrity": "sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.7", - "@jridgewell/gen-mapping": "^0.3.2", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.6.tgz", - "integrity": "sha512-KT10c1oWEpmrIRYnthbzHgoOf6B+Xd6a5yhdbNtdhtG7aO1or5HViuf1TQR36xY/QprXA5nvxO6nAjhJ4y38jw==", - "dev": true, - "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.6.tgz", - "integrity": "sha512-vFjbfhNCzqdeAtZflUFrG5YIFqGTqsctrtkZ1D/NB0mDW9TwW3GmmUepYY4G9wCET5rY5ugz4OGTcLd614IzQg==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.20.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.6.tgz", - "integrity": "sha512-YfDzdnoxHGV8CzqHGyCbFvXg5QESPFkXlHtvdCkesLjjVMT2Adxe4FGUR5ChIb3DxSaXO12iIOCWoXdsUVwnqw==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.6", - "@babel/helper-function-name": "^7.18.6", - "@babel/helper-member-expression-to-functions": "^7.18.6", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz", - "integrity": "sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", - "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.13.0", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/traverse": "^7.13.0", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz", - "integrity": "sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.6.tgz", - "integrity": "sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.18.6", - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.6.tgz", - "integrity": "sha512-CeHxqwwipekotzPDUuJOfIMtcIHBuc7WAzLmTYWctVigqS5RktNMQ5bEwQSuGewzYnCtTWa3BARXeiLxDTv+Ng==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.6.tgz", - "integrity": "sha512-L//phhB4al5uucwzlimruukHB3jRd5JGClwRMD/ROrVjXfLqovYnvQrK/JK36WYyVwGGO7OD3kMyVTjx+WVPhw==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.6", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.6", - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz", - "integrity": "sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.6.tgz", - "integrity": "sha512-z5wbmV55TveUPZlCLZvxWHtrjuJd+8inFhk7DG0WW87/oJuGDcjDiu7HIvGcpf5464L6xKCg3vNkmlVVz9hwyQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.6", - "@babel/helper-wrap-function": "^7.18.6", - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.6.tgz", - "integrity": "sha512-fTf7zoXnUGl9gF25fXCWE26t7Tvtyn6H4hkLSYhATwJvw2uYxd3aoXplMSe0g9XbwK7bmxNes7+FGO0rB/xC0g==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.6", - "@babel/helper-member-expression-to-functions": "^7.18.6", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/traverse": "^7.18.6", - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.6.tgz", - "integrity": "sha512-4KoLhwGS9vGethZpAhYnMejWkX64wsnHPDwvOsKWU6Fg4+AlK2Jz3TyjQLMEPvz+1zemi/WBdkYxCD0bAfIkiw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.18.6.tgz", - "integrity": "sha512-I5/LZfozwMNbwr/b1vhhuYD+J/mU+gfGAj5td7l5Rv9WYmH6i3Om69WGKNmlIpsVW/mF6O5bvTKbvDQZVgjqOw==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.18.6", - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.6", - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.6.tgz", - "integrity": "sha512-vzSiiqbQOghPngUYt/zWGvK3LAsPhz55vc9XNN0xAl2gV4ieShI2OQli5duxWHD+72PZPTKAcfcZDE1Cwc5zsQ==", - "dev": true, - "dependencies": { - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.6", - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.6.tgz", - "integrity": "sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.6.tgz", - "integrity": "sha512-Udgu8ZRgrBrttVz6A0EVL0SJ1z+RLbIeqsu632SA1hf0awEppD6TvdznoH+orIF8wtFFAV/Enmw9Y+9oV8TQcw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.6.tgz", - "integrity": "sha512-WAz4R9bvozx4qwf74M+sfqPMKfSqwM0phxPTR6iJIi8robgzXwkEgmeJG1gEKhm6sDqT/U9aV3lfcqybIpev8w==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", - "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.6.tgz", - "integrity": "sha512-zr/QcUlUo7GPo6+X1wC98NJADqmy5QTFWWhqeQWiki4XHafJtLl/YMGkmRB2szDD2IYJCCdBTd4ElwhId9T7Xw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.6.tgz", - "integrity": "sha512-zMo66azZth/0tVd7gmkxOkOjs2rpHyhpcFo565PUP37hSp6hSd9uUKIfTDFMz58BwqgQKhJ9YxtM5XddjXVn+Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.6.tgz", - "integrity": "sha512-9yuM6wr4rIsKa1wlUAbZEazkCrgw2sMPEXCr4Rnwetu7cEW1NydkCWytLuYletbf8vFxdJxFhwEZqMpOx2eZyw==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.18.6", - "@babel/helper-compilation-targets": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.6.tgz", - "integrity": "sha512-PatI6elL5eMzoypFAiYDpYQyMtXTn+iMhuxxQt5mAXD4fEmKorpSI3PHd+i3JXBJN3xyA6MvJv7at23HffFHwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.6", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", - "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz", - "integrity": "sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", - "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", - "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.6.tgz", - "integrity": "sha512-pRqwb91C42vs1ahSAWJkxOxU1RHWDn16XAa6ggQ72wjLlWyYeAcLvTtE0aM8ph3KNydy9CQF2nLYcjq1WysgxQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.6.tgz", - "integrity": "sha512-XTg8XW/mKpzAF3actL554Jl/dOYoJtv3l8fxaEczpgz84IeeVf+T1u2CSvPHuZbt0w3JkIx4rdn/MRQI7mo0HQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.6", - "@babel/helper-function-name": "^7.18.6", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.6.tgz", - "integrity": "sha512-9repI4BhNrR0KenoR9vm3/cIc1tSBIo+u1WVjKCAynahj25O8zfbiE6JtAtHPGQSs4yZ+bA8mRasRP+qc+2R5A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.6.tgz", - "integrity": "sha512-tgy3u6lRp17ilY8r1kP4i2+HDUwxlVqq3RTc943eAWSzGgpU1qhiKpqZ5CMyHReIYPHdo3Kg8v8edKtDqSVEyQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.6.tgz", - "integrity": "sha512-NJU26U/208+sxYszf82nmGYqVF9QN8py2HFTblPT9hbawi8+1C5a9JubODLTGFuT0qlkqVinmkwOD13s0sZktg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "dev": true, - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.6.tgz", - "integrity": "sha512-WAjoMf4wIiSsy88KmG7tgj2nFdEK7E46tArVtcgED7Bkj6Fg/tG5SbvNIOKxbFS2VFgNh6+iaPswBeQZm4ox8w==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.6.tgz", - "integrity": "sha512-kJha/Gbs5RjzIu0CxZwf5e3aTTSlhZnHMT8zPWnJMjNpLOUgqevg+PN5oMH68nMCXnfiMo4Bhgxqj59KHTlAnA==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.18.6", - "@babel/helper-function-name": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.6.tgz", - "integrity": "sha512-x3HEw0cJZVDoENXOp20HlypIHfl0zMIhMVZEBVTfmqbObIpsMxMbmU5nOEO8R7LYT+z5RORKPlTI5Hj4OsO9/Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz", - "integrity": "sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz", - "integrity": "sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.6.tgz", - "integrity": "sha512-UbPYpXxLjTw6w6yXX2BYNxF3p6QY225wcTkfQCy3OMnSlS/C3xGtwUjEzGkldb/sy6PWLiCQ3NbYfjWUTI3t4g==", - "dev": true, - "dependencies": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz", - "integrity": "sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.6.tgz", - "integrity": "sha512-FjdqgMv37yVl/gwvzkcB+wfjRI8HQmc5EgOG9iGNvUY1ok+TjsoaMP7IqCDZBhkFcM5f3OPVMs6Dmp03C5k4/A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", - "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "regenerator-transform": "^0.15.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.6.tgz", - "integrity": "sha512-ayT53rT/ENF8WWexIRg9AiV9h0aIteyWn5ptfZTZQrjk/+f3WdrJGCY4c9wcgl2+MKkKPhzbYp97FTsquZpDCw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.6.tgz", - "integrity": "sha512-UuqlRrQmT2SWRvahW46cGSany0uTlcj8NYOS5sRGYi8FxPYPoLd5DDmMd32ZXEj2Jq+06uGVQKHxa/hJx2EzKw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.6.tgz", - "integrity": "sha512-7m71iS/QhsPk85xSjFPovHPcH3H9qeyzsujhTc+vcdnsXavoWYJ74zx0lP5RhpC5+iDnVLO+PPMHzC11qels1g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.6.tgz", - "integrity": "sha512-XNRwQUXYMP7VLuy54cr/KS/WeL3AZeORhrmeZ7iewgu+X2eBqmpaLI/hzqr9ZxCeUoq0ASK4GUzSM0BDhZkLFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.6.tgz", - "integrity": "sha512-WrthhuIIYKrEFAwttYzgRNQ5hULGmwTj+D6l7Zdfsv5M7IWV/OZbUfbeL++Qrzx1nVJwWROIFhCHRYQV4xbPNw==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.18.6", - "@babel/helper-compilation-targets": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.6", - "@babel/plugin-proposal-async-generator-functions": "^7.18.6", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.6", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.6", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.18.6", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.6", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.18.6", - "@babel/plugin-transform-classes": "^7.18.6", - "@babel/plugin-transform-computed-properties": "^7.18.6", - "@babel/plugin-transform-destructuring": "^7.18.6", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.6", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.6", - "@babel/plugin-transform-function-name": "^7.18.6", - "@babel/plugin-transform-literals": "^7.18.6", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.18.6", - "@babel/plugin-transform-modules-commonjs": "^7.18.6", - "@babel/plugin-transform-modules-systemjs": "^7.18.6", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.18.6", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.18.6", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.18.6", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.6", - "@babel/plugin-transform-typeof-symbol": "^7.18.6", - "@babel/plugin-transform-unicode-escapes": "^7.18.6", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.18.6", - "babel-plugin-polyfill-corejs2": "^0.3.1", - "babel-plugin-polyfill-corejs3": "^0.5.2", - "babel-plugin-polyfill-regenerator": "^0.3.1", - "core-js-compat": "^3.22.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.6.tgz", - "integrity": "sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ==", - "dependencies": { - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.6.tgz", - "integrity": "sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.6", - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.6.tgz", - "integrity": "sha512-zS/OKyqmD7lslOtFqbscH6gMLFYOfG1YPqCKfAW5KrTeolKqvB8UelR49Fpr6y93kYkW2Ik00mT1LOGiAGvizw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.6", - "@babel/helper-function-name": "^7.18.6", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.18.6", - "@babel/types": "^7.18.6", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/types": { - "version": "7.18.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.7.tgz", - "integrity": "sha512-QG3yxTcTIBoAcQmkCs+wAPYZhu7Dk9rXKacINfNbdJDNERTbLQbHGyVG8q/YGMPeCJRIhSY0+fTc5+xuh6WPSQ==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@codemirror/commands": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.1.2.tgz", - "integrity": "sha512-sO3jdX1s0pam6lIdeSJLMN3DQ6mPEbM4yLvyKkdqtmd/UDwhXA5+AwFJ89rRXm6vTeOXBsE5cAmlos/t7MJdgg==", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-xml": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.0.0.tgz", - "integrity": "sha512-M/HLWxIiP956xGjtrxkeHkCmDGVQGKu782x8pOH5CLJIMkWtiB1DWfDoDHqpFjdEE9dkfcqPWvYfVi6GbhuXEg==", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@lezer/common": "^1.0.0", - "@lezer/xml": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-xml/node_modules/@codemirror/autocomplete": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.0.2.tgz", - "integrity": "sha512-9PDjnllmXan/7Uax87KGORbxerDJ/cu10SB+n4Jz0zXMEvIh3+TGgZxhIvDOtaQ4jDBQEM7kHYW4vLdQB0DGZQ==", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0" - }, - "peerDependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@codemirror/language": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.2.1.tgz", - "integrity": "sha512-MC3svxuvIj0MRpFlGHxLS6vPyIdbTr2KKPEW46kCoCXw2ktb4NTkpkPBI/lSP/FoNXLCBJ0mrnUi1OoZxtpW1Q==", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0", - "style-mod": "^4.0.0" - } - }, - "node_modules/@codemirror/state": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.1.2.tgz", - "integrity": "sha512-Mxff85Hp5va+zuj+H748KbubXjrinX/k28lj43H14T2D0+4kuvEFIEIO7hCEcvBT8ubZyIelt9yGOjj2MWOEQA==" - }, - "node_modules/@codemirror/view": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.0.2.tgz", - "integrity": "sha512-mnVT/q1JvKPjpmjXJNeCi/xHyaJ3abGJsumIVpdQ1nE1MXAyHf7GHWt8QpWMUvDiqF0j+inkhVR2OviTdFFX7Q==", - "dependencies": { - "@codemirror/state": "^6.0.0", - "style-mod": "^4.0.0", - "w3c-keyname": "^2.2.4" - } - }, - "node_modules/@commitlint/cli": { - "version": "17.1.2", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-17.1.2.tgz", - "integrity": "sha512-h/4Hlka3bvCLbnxf0Er2ri5A44VMlbMSkdTRp8Adv2tRiklSTRIoPGs7OEXDv3EoDs2AAzILiPookgM4Gi7LOw==", - "dev": true, - "dependencies": { - "@commitlint/format": "^17.0.0", - "@commitlint/lint": "^17.1.0", - "@commitlint/load": "^17.1.2", - "@commitlint/read": "^17.1.0", - "@commitlint/types": "^17.0.0", - "execa": "^5.0.0", - "lodash": "^4.17.19", - "resolve-from": "5.0.0", - "resolve-global": "1.0.0", - "yargs": "^17.0.0" - }, - "bin": { - "commitlint": "cli.js" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/cli/node_modules/yargs": { - "version": "17.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@commitlint/cli/node_modules/yargs-parser": { - "version": "21.0.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/@commitlint/config-conventional": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-17.1.0.tgz", - "integrity": "sha512-WU2p0c9/jLi8k2q2YrDV96Y8XVswQOceIQ/wyJvQxawJSCasLdRB3kUIYdNjOCJsxkpoUlV/b90ZPxp1MYZDiA==", - "dev": true, - "dependencies": { - "conventional-changelog-conventionalcommits": "^5.0.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/config-validator": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-17.1.0.tgz", - "integrity": "sha512-Q1rRRSU09ngrTgeTXHq6ePJs2KrI+axPTgkNYDWSJIuS1Op4w3J30vUfSXjwn5YEJHklK3fSqWNHmBhmTR7Vdg==", - "dev": true, - "dependencies": { - "@commitlint/types": "^17.0.0", - "ajv": "^8.11.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/ensure": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-17.0.0.tgz", - "integrity": "sha512-M2hkJnNXvEni59S0QPOnqCKIK52G1XyXBGw51mvh7OXDudCmZ9tZiIPpU882p475Mhx48Ien1MbWjCP1zlyC0A==", - "dev": true, - "dependencies": { - "@commitlint/types": "^17.0.0", - "lodash": "^4.17.19" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/execute-rule": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-17.0.0.tgz", - "integrity": "sha512-nVjL/w/zuqjCqSJm8UfpNaw66V9WzuJtQvEnCrK4jDw6qKTmZB+1JQ8m6BQVZbNBcwfYdDNKnhIhqI0Rk7lgpQ==", - "dev": true, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/format": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-17.0.0.tgz", - "integrity": "sha512-MZzJv7rBp/r6ZQJDEodoZvdRM0vXu1PfQvMTNWFb8jFraxnISMTnPBWMMjr2G/puoMashwaNM//fl7j8gGV5lA==", - "dev": true, - "dependencies": { - "@commitlint/types": "^17.0.0", - "chalk": "^4.1.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/is-ignored": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-17.1.0.tgz", - "integrity": "sha512-JITWKDMHhIh8IpdIbcbuH9rEQJty1ZWelgjleTFrVRAcEwN/sPzk1aVUXRIZNXMJWbZj8vtXRJnFihrml8uECQ==", - "dev": true, - "dependencies": { - "@commitlint/types": "^17.0.0", - "semver": "7.3.7" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/is-ignored/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@commitlint/lint": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-17.1.0.tgz", - "integrity": "sha512-ltpqM2ogt/+SDhUaScFo0MdscncEF96lvQTPMM/VTTWlw7sTGLLWkOOppsee2MN/uLNNWjQ7kqkd4h6JqoM9AQ==", - "dev": true, - "dependencies": { - "@commitlint/is-ignored": "^17.1.0", - "@commitlint/parse": "^17.0.0", - "@commitlint/rules": "^17.0.0", - "@commitlint/types": "^17.0.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/load": { - "version": "17.1.2", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-17.1.2.tgz", - "integrity": "sha512-sk2p/jFYAWLChIfOIp/MGSIn/WzZ0vkc3afw+l4X8hGEYkvDe4gQUUAVxjl/6xMRn0HgnSLMZ04xXh5pkTsmgg==", - "dev": true, - "dependencies": { - "@commitlint/config-validator": "^17.1.0", - "@commitlint/execute-rule": "^17.0.0", - "@commitlint/resolve-extends": "^17.1.0", - "@commitlint/types": "^17.0.0", - "@types/node": "^14.0.0", - "chalk": "^4.1.0", - "cosmiconfig": "^7.0.0", - "cosmiconfig-typescript-loader": "^4.0.0", - "lodash": "^4.17.19", - "resolve-from": "^5.0.0", - "ts-node": "^10.8.1", - "typescript": "^4.6.4" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/load/node_modules/@types/node": { - "version": "14.18.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.26.tgz", - "integrity": "sha512-0b+utRBSYj8L7XAp0d+DX7lI4cSmowNaaTkk6/1SKzbKkG+doLuPusB9EOvzLJ8ahJSk03bTLIL6cWaEd4dBKA==", - "dev": true - }, - "node_modules/@commitlint/message": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-17.0.0.tgz", - "integrity": "sha512-LpcwYtN+lBlfZijHUdVr8aNFTVpHjuHI52BnfoV01TF7iSLnia0jttzpLkrLmI8HNQz6Vhr9UrxDWtKZiMGsBw==", - "dev": true, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/parse": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-17.0.0.tgz", - "integrity": "sha512-cKcpfTIQYDG1ywTIr5AG0RAiLBr1gudqEsmAGCTtj8ffDChbBRxm6xXs2nv7GvmJN7msOt7vOKleLvcMmRa1+A==", - "dev": true, - "dependencies": { - "@commitlint/types": "^17.0.0", - "conventional-changelog-angular": "^5.0.11", - "conventional-commits-parser": "^3.2.2" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/read": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-17.1.0.tgz", - "integrity": "sha512-73BoFNBA/3Ozo2JQvGsE0J8SdrJAWGfZQRSHqvKaqgmY042Su4gXQLqvAzgr55S9DI1l9TiU/5WDuh8IE86d/g==", - "dev": true, - "dependencies": { - "@commitlint/top-level": "^17.0.0", - "@commitlint/types": "^17.0.0", - "fs-extra": "^10.0.0", - "git-raw-commits": "^2.0.0", - "minimist": "^1.2.6" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/read/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@commitlint/read/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@commitlint/resolve-extends": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-17.1.0.tgz", - "integrity": "sha512-jqKm00LJ59T0O8O4bH4oMa4XyJVEOK4GzH8Qye9XKji+Q1FxhZznxMV/bDLyYkzbTodBt9sL0WLql8wMtRTbqQ==", - "dev": true, - "dependencies": { - "@commitlint/config-validator": "^17.1.0", - "@commitlint/types": "^17.0.0", - "import-fresh": "^3.0.0", - "lodash": "^4.17.19", - "resolve-from": "^5.0.0", - "resolve-global": "^1.0.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/rules": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-17.0.0.tgz", - "integrity": "sha512-45nIy3dERKXWpnwX9HeBzK5SepHwlDxdGBfmedXhL30fmFCkJOdxHyOJsh0+B0RaVsLGT01NELpfzJUmtpDwdQ==", - "dev": true, - "dependencies": { - "@commitlint/ensure": "^17.0.0", - "@commitlint/message": "^17.0.0", - "@commitlint/to-lines": "^17.0.0", - "@commitlint/types": "^17.0.0", - "execa": "^5.0.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/to-lines": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-17.0.0.tgz", - "integrity": "sha512-nEi4YEz04Rf2upFbpnEorG8iymyH7o9jYIVFBG1QdzebbIFET3ir+8kQvCZuBE5pKCtViE4XBUsRZz139uFrRQ==", - "dev": true, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/top-level": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-17.0.0.tgz", - "integrity": "sha512-dZrEP1PBJvodNWYPOYiLWf6XZergdksKQaT6i1KSROLdjf5Ai0brLOv5/P+CPxBeoj3vBxK4Ax8H1Pg9t7sHIQ==", - "dev": true, - "dependencies": { - "find-up": "^5.0.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/top-level/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@commitlint/top-level/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@commitlint/top-level/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@commitlint/top-level/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@commitlint/types": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-17.0.0.tgz", - "integrity": "sha512-hBAw6U+SkAT5h47zDMeOu3HSiD0SODw4Aq7rRNh1ceUmL7GyLKYhPbUvlRWqZ65XjBLPHZhFyQlRaPNz8qvUyQ==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@csstools/postcss-cascade-layers": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", - "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", - "dev": true, - "dependencies": { - "@csstools/selector-specificity": "^2.0.2", - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-color-function": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", - "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", - "dev": true, - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-font-format-keywords": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", - "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-hwb-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", - "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-ic-unit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", - "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", - "dev": true, - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", - "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", - "dev": true, - "dependencies": { - "@csstools/selector-specificity": "^2.0.0", - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-nested-calc": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", - "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-normalize-display-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", - "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-oklab-function": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", - "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", - "dev": true, - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-progressive-custom-properties": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", - "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.3" - } - }, - "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", - "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", - "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", - "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-unset-value": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", - "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", - "dev": true, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/selector-specificity": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", - "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", - "dev": true, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2", - "postcss-selector-parser": "^6.0.10" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/@floating-ui/core": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.0.1.tgz", - "integrity": "sha512-bO37brCPfteXQfFY0DyNDGB3+IMe4j150KFQcgJ5aBP295p9nBGeHEs/p0czrRbtlHq4Px/yoPXO/+dOCcF4uA==" - }, - "node_modules/@floating-ui/dom": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.0.2.tgz", - "integrity": "sha512-5X9WSvZ8/fjy3gDu8yx9HAA4KG1lazUN2P4/VnaXLxTO9Dz53HI1oYoh1OlhqFNlHgGDiwFX5WhFCc2ljbW3yA==", - "dependencies": { - "@floating-ui/core": "^1.0.1" - } - }, - "node_modules/@foliojs-fork/fontkit": { - "version": "1.9.1", - "license": "MIT", - "dependencies": { - "@foliojs-fork/restructure": "^2.0.2", - "brfs": "^2.0.0", - "brotli": "^1.2.0", - "browserify-optional": "^1.0.1", - "clone": "^1.0.4", - "deep-equal": "^1.0.0", - "dfa": "^1.2.0", - "tiny-inflate": "^1.0.2", - "unicode-properties": "^1.2.2", - "unicode-trie": "^2.0.0" - } - }, - "node_modules/@foliojs-fork/linebreak": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "base64-js": "1.3.1", - "brfs": "^2.0.2", - "unicode-trie": "^2.0.0" - } - }, - "node_modules/@foliojs-fork/pdfkit": { - "version": "0.12.3", - "license": "MIT", - "dependencies": { - "@foliojs-fork/fontkit": "^1.9.1", - "@foliojs-fork/linebreak": "^1.1.1", - "crypto-js": "^4.0.0", - "png-js": "^1.0.0" - } - }, - "node_modules/@foliojs-fork/restructure": { - "version": "2.0.2", - "license": "MIT" - }, - "node_modules/@github/clipboard-copy-element": { - "version": "1.1.2", - "license": "MIT" - }, - "node_modules/@github/hotkey": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@github/hotkey/-/hotkey-2.0.1.tgz", - "integrity": "sha512-qKXjAJjtheJbf4ie3hi8IwrHWJZHB5qdojR6JGo6jvQNPpsdUbk/NIdU8sxu4PW41CjW80vfciDMu3MAP3j2Fg==" - }, - "node_modules/@github/markdown-toolbar-element": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@github/markdown-toolbar-element/-/markdown-toolbar-element-2.1.1.tgz", - "integrity": "sha512-J++rpd5H9baztabJQB82h26jtueOeBRSTqetk9Cri+Lj/s28ndu6Tovn0uHQaOKtBWDobFunk9b5pP5vcqt7cA==" - }, - "node_modules/@github/time-elements": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@github/time-elements/-/time-elements-3.1.4.tgz", - "integrity": "sha512-DTe/w0uKVeciKzGtYadNdfS8D86pXdGF+OrKg+vi8PKlotJ45zAc26zNpmmfCcMblBBg2+uoi3OxmUm7am/0sg==" - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.5.tgz", - "integrity": "sha512-XVVDtp+dVvRxMoxSiSfasYaG02VEe1qH5cKgMQJWhol6HwzbcqoCMJi8dAGoYAO57jhUyhI6cWuRiTcRaDaYug==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz", - "integrity": "sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@lezer/common": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.0.tgz", - "integrity": "sha512-ohydQe+Hb+w4oMDvXzs8uuJd2NoA3D8YDcLiuDsLqH+yflDTPEpgCsWI3/6rH5C3BAedtH1/R51dxENldQceEA==" - }, - "node_modules/@lezer/highlight": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.0.0.tgz", - "integrity": "sha512-nsCnNtim90UKsB5YxoX65v3GEIw3iCHw9RM2DtdgkiqAbKh9pCdvi8AWNwkYf10Lu6fxNhXPpkpHbW6mihhvJA==", - "dependencies": { - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@lezer/lr": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.1.0.tgz", - "integrity": "sha512-Iad04uVwk1PvSnj25mqj7zEEIRAsasbsTRmVzI0AUTs/+1Dz1//iYAaoLr7A+Xa7bZDfql5MKTxZmSlkYZD3Dg==", - "dependencies": { - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@lezer/xml": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.0.tgz", - "integrity": "sha512-73iI9UK8iqSvWtLlOEl/g+50ivwQn8Ge6foHVN66AXUS1RccFnAoc7BYU8b3c8/rP6dfCOGqAGaWLxBzhj60MA==", - "dependencies": { - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lit/reactive-element": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.4.1.tgz", - "integrity": "sha512-qDv4851VFSaBWzpS02cXHclo40jsbAjRXnebNXpm0uVg32kCneZPo9RYVQtrTNICtZ+1wAYHu1ZtxWSWMbKrBw==" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.3", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.3", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@octokit/auth-token": { - "version": "2.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^6.0.3" - } - }, - "node_modules/@octokit/core": { - "version": "3.5.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^2.4.4", - "@octokit/graphql": "^4.5.8", - "@octokit/request": "^5.6.0", - "@octokit/request-error": "^2.0.5", - "@octokit/types": "^6.0.3", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/endpoint": { - "version": "6.0.12", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^6.0.3", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/graphql": { - "version": "4.8.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/request": "^5.6.0", - "@octokit/types": "^6.0.3", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "11.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "2.17.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^6.34.0" - }, - "peerDependencies": { - "@octokit/core": ">=2" - } - }, - "node_modules/@octokit/plugin-request-log": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@octokit/core": ">=3" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "5.13.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^6.34.0", - "deprecation": "^2.3.1" - }, - "peerDependencies": { - "@octokit/core": ">=3" - } - }, - "node_modules/@octokit/request": { - "version": "5.6.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^6.0.1", - "@octokit/request-error": "^2.1.0", - "@octokit/types": "^6.16.1", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.1", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/request-error": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^6.0.3", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - }, - "node_modules/@octokit/rest": { - "version": "18.12.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/core": "^3.5.1", - "@octokit/plugin-paginate-rest": "^2.16.8", - "@octokit/plugin-request-log": "^1.0.4", - "@octokit/plugin-rest-endpoint-methods": "^5.12.0" - } - }, - "node_modules/@octokit/types": { - "version": "6.34.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^11.2.0" - } - }, - "node_modules/@rollup/plugin-babel": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", - "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.10.4", - "@rollup/pluginutils": "^3.1.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "@types/babel__core": "^7.1.9", - "rollup": "^1.20.0||^2.0.0" - }, - "peerDependenciesMeta": { - "@types/babel__core": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", - "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "@types/resolve": "1.17.1", - "builtin-modules": "^3.1.0", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/plugin-replace": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", - "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "magic-string": "^0.25.7" - }, - "peerDependencies": { - "rollup": "^1.20.0 || ^2.0.0" - } - }, - "node_modules/@rollup/plugin-replace/node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@semantic-release/changelog": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@semantic-release/error": "^3.0.0", - "aggregate-error": "^3.0.0", - "fs-extra": "^9.0.0", - "lodash": "^4.17.4" - }, - "engines": { - "node": ">=14.17" - }, - "peerDependencies": { - "semantic-release": ">=18.0.0" - } - }, - "node_modules/@semantic-release/commit-analyzer": { - "version": "9.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "conventional-changelog-angular": "^5.0.0", - "conventional-commits-filter": "^2.0.0", - "conventional-commits-parser": "^3.2.3", - "debug": "^4.0.0", - "import-from": "^4.0.0", - "lodash": "^4.17.4", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=14.17" - }, - "peerDependencies": { - "semantic-release": ">=18.0.0-beta.1" - } - }, - "node_modules/@semantic-release/commit-analyzer/node_modules/import-from": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/error": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.17" - } - }, - "node_modules/@semantic-release/exec": { - "version": "6.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@semantic-release/error": "^3.0.0", - "aggregate-error": "^3.0.0", - "debug": "^4.0.0", - "execa": "^5.0.0", - "lodash": "^4.17.4", - "parse-json": "^5.0.0" - }, - "engines": { - "node": ">=14.17" - }, - "peerDependencies": { - "semantic-release": ">=18.0.0" - } - }, - "node_modules/@semantic-release/exec/node_modules/parse-json": { - "version": "5.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/git": { - "version": "10.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@semantic-release/error": "^3.0.0", - "aggregate-error": "^3.0.0", - "debug": "^4.0.0", - "dir-glob": "^3.0.0", - "execa": "^5.0.0", - "lodash": "^4.17.4", - "micromatch": "^4.0.0", - "p-reduce": "^2.0.0" - }, - "engines": { - "node": ">=14.17" - }, - "peerDependencies": { - "semantic-release": ">=18.0.0" - } - }, - "node_modules/@semantic-release/github": { - "version": "8.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/rest": "^18.0.0", - "@semantic-release/error": "^2.2.0", - "aggregate-error": "^3.0.0", - "bottleneck": "^2.18.1", - "debug": "^4.0.0", - "dir-glob": "^3.0.0", - "fs-extra": "^10.0.0", - "globby": "^11.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "issue-parser": "^6.0.0", - "lodash": "^4.17.4", - "mime": "^3.0.0", - "p-filter": "^2.0.0", - "p-retry": "^4.0.0", - "url-join": "^4.0.0" - }, - "engines": { - "node": ">=14.17" - }, - "peerDependencies": { - "semantic-release": ">=18.0.0-beta.1" - } - }, - "node_modules/@semantic-release/github/node_modules/@semantic-release/error": { - "version": "2.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@semantic-release/github/node_modules/@tootallnate/once": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@semantic-release/github/node_modules/fs-extra": { - "version": "10.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@semantic-release/github/node_modules/http-proxy-agent": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@semantic-release/github/node_modules/universalify": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@semantic-release/gitlab": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/@semantic-release/gitlab/-/gitlab-9.4.2.tgz", - "integrity": "sha512-ZfMe4N613C/tqfRdxDPprOoBA7HHyHFHvk+TA4HqOmNpgUKiifyJPpYJsc3PLNtec9I0TA1OOTuY82VDG+FVXw==", - "dev": true, - "dependencies": { - "@semantic-release/error": "^3.0.0", - "aggregate-error": "^3.0.0", - "debug": "^4.0.0", - "dir-glob": "^3.0.0", - "escape-string-regexp": "^3.0.0", - "form-data": "^4.0.0", - "fs-extra": "^10.0.0", - "globby": "^11.0.0", - "got": "^11.0.0", - "hpagent": "^1.0.0", - "lodash": "^4.17.11", - "parse-url": "^8.0.0", - "url-join": "^4.0.0" - }, - "engines": { - "node": ">=14.17" - }, - "peerDependencies": { - "semantic-release": ">=18.0.0" - } - }, - "node_modules/@semantic-release/gitlab/node_modules/escape-string-regexp": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/gitlab/node_modules/form-data": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@semantic-release/gitlab/node_modules/fs-extra": { - "version": "10.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@semantic-release/gitlab/node_modules/universalify": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@semantic-release/npm": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-9.0.0.tgz", - "integrity": "sha512-hj2jqayS2SPUmFtCMCOQMX975uMDfRoymj1HvMSwYdaoI6hVZvhrTFPBgJeM85O0C+G3IFviAUar5gel/1VGDQ==", - "dev": true, - "dependencies": { - "@semantic-release/error": "^3.0.0", - "aggregate-error": "^3.0.0", - "execa": "^5.0.0", - "fs-extra": "^10.0.0", - "lodash": "^4.17.15", - "nerf-dart": "^1.0.0", - "normalize-url": "^6.0.0", - "npm": "^8.3.0", - "rc": "^1.2.8", - "read-pkg": "^5.0.0", - "registry-auth-token": "^4.0.0", - "semver": "^7.1.2", - "tempy": "^1.0.0" - }, - "engines": { - "node": ">=16 || ^14.17" - }, - "peerDependencies": { - "semantic-release": ">=19.0.0" - } - }, - "node_modules/@semantic-release/npm/node_modules/fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@semantic-release/npm/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@semantic-release/npm/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@semantic-release/release-notes-generator": { - "version": "10.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "conventional-changelog-angular": "^5.0.0", - "conventional-changelog-writer": "^5.0.0", - "conventional-commits-filter": "^2.0.0", - "conventional-commits-parser": "^3.2.3", - "debug": "^4.0.0", - "get-stream": "^6.0.0", - "import-from": "^4.0.0", - "into-stream": "^6.0.0", - "lodash": "^4.17.4", - "read-pkg-up": "^7.0.0" - }, - "engines": { - "node": ">=14.17" - }, - "peerDependencies": { - "semantic-release": ">=18.0.0-beta.1" - } - }, - "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/release-notes-generator/node_modules/import-from": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@sindresorhus/is": { - "version": "4.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@stencil/core": { - "version": "2.5.2", - "license": "MIT", - "bin": { - "stencil": "bin/stencil" - }, - "engines": { - "node": ">=12.10.0", - "npm": ">=6.0.0" - } - }, - "node_modules/@surma/rollup-plugin-off-main-thread": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", - "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", - "dev": true, - "dependencies": { - "ejs": "^3.1.6", - "json5": "^2.2.0", - "magic-string": "^0.25.0", - "string.prototype.matchall": "^4.0.6" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@tailwindcss/forms": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.3.tgz", - "integrity": "sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==", - "dev": true, - "dependencies": { - "mini-svg-data-uri": "^1.2.3" - }, - "peerDependencies": { - "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1" - } - }, - "node_modules/@tailwindcss/line-clamp": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/line-clamp/-/line-clamp-0.4.2.tgz", - "integrity": "sha512-HFzAQuqYCjyy/SX9sLGB1lroPzmcnWv1FHkIpmypte10hptf4oPUfucryMKovZh2u0uiS9U5Ty3GghWfEJGwVw==", - "dev": true, - "peerDependencies": { - "tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1" - } - }, - "node_modules/@tailwindcss/nesting": { - "version": "0.0.0-insiders.565cd3e", - "resolved": "https://registry.npmjs.org/@tailwindcss/nesting/-/nesting-0.0.0-insiders.565cd3e.tgz", - "integrity": "sha512-WhHoFBx19TnH/c+xLwT/sxei6+4RpdfiyG3MYXfmLaMsADmVqBkF7B6lDalgZD9YdM459MF7DtxVbWkOrV7IaQ==", - "dependencies": { - "postcss-nested": "^5.0.5" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/@tailwindcss/typography": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.7.tgz", - "integrity": "sha512-JTTSTrgZfp6Ki4svhPA4mkd9nmQ/j9EfE7SbHJ1cLtthKkpW2OxsFXzSmxbhYbEkfNIyAyhle5p4SYyKRbz/jg==", - "dev": true, - "dependencies": { - "lodash.castarray": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.merge": "^4.6.2", - "postcss-selector-parser": "6.0.10" - }, - "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true - }, - "node_modules/@types/cacheable-request": { - "version": "6.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "*", - "@types/node": "*", - "@types/responselike": "*" - } - }, - "node_modules/@types/color-name": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/debounce": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-epMsEE85fi4lfmJUH/89/iV/LI+F5CvNIvmgs5g5jYFPfhO2S/ae8WSsLOKWdwtoaZw9Q2IhJ4tQ5tFCcS/4HA==", - "dev": true - }, - "node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, - "node_modules/@types/fscreen": { - "version": "1.0.1", - "license": "MIT" - }, - "node_modules/@types/geojson": { - "version": "7946.0.8", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "node_modules/@types/keyv": { - "version": "3.1.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/leaflet": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.8.0.tgz", - "integrity": "sha512-+sXFmiJTFdhaXXIGFlV5re9AdqtAODoXbGAvxx02e5SHXL3ir7ClP5J7pahO8VmzKY3dth4RUS1nf2BTT+DW1A==", - "dev": true, - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/marked": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.7.tgz", - "integrity": "sha512-eEAhnz21CwvKVW+YvRvcTuFKNU9CV1qH+opcgVK3pIMI6YZzDm6gc8o2vHjldFk6MGKt5pueSB7IOpvpx5Qekw==", - "dev": true - }, - "node_modules/@types/minimist": { - "version": "1.2.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "17.0.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/resolve": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", - "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/responselike": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/retry": { - "version": "0.12.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/trusted-types": { - "version": "2.0.2", - "license": "MIT" - }, - "node_modules/@types/wavesurfer.js": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/wavesurfer.js/-/wavesurfer.js-6.0.3.tgz", - "integrity": "sha512-5Sb5s3pEkOmDosaaP1DWp1Unnx8HhVorm5608TIVdT5jCMvJ6eqM19UD8n7DEbJ7rzreS9RqHmzR8TlcdQBvbA==", - "dev": true, - "dependencies": { - "@types/debounce": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.40.0.tgz", - "integrity": "sha512-FIBZgS3DVJgqPwJzvZTuH4HNsZhHMa9SjxTKAZTlMsPw/UzpEjcf9f4dfgDJEHjK+HboUJo123Eshl6niwEm/Q==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.40.0", - "@typescript-eslint/type-utils": "5.40.0", - "@typescript-eslint/utils": "5.40.0", - "debug": "^4.3.4", - "ignore": "^5.2.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.40.0.tgz", - "integrity": "sha512-Ah5gqyX2ySkiuYeOIDg7ap51/b63QgWZA7w6AHtFrag7aH0lRQPbLzUjk0c9o5/KZ6JRkTTDKShL4AUrQa6/hw==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.40.0", - "@typescript-eslint/types": "5.40.0", - "@typescript-eslint/typescript-estree": "5.40.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.40.0.tgz", - "integrity": "sha512-d3nPmjUeZtEWRvyReMI4I1MwPGC63E8pDoHy0BnrYjnJgilBD3hv7XOiETKLY/zTwI7kCnBDf2vWTRUVpYw0Uw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.40.0", - "@typescript-eslint/visitor-keys": "5.40.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.40.0.tgz", - "integrity": "sha512-nfuSdKEZY2TpnPz5covjJqav+g5qeBqwSHKBvz7Vm1SAfy93SwKk/JeSTymruDGItTwNijSsno5LhOHRS1pcfw==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "5.40.0", - "@typescript-eslint/utils": "5.40.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.40.0.tgz", - "integrity": "sha512-V1KdQRTXsYpf1Y1fXCeZ+uhjW48Niiw0VGt4V8yzuaDTU8Z1Xl7yQDyQNqyAFcVhpYXIVCEuxSIWTsLDpHgTbw==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.40.0.tgz", - "integrity": "sha512-b0GYlDj8TLTOqwX7EGbw2gL5EXS2CPEWhF9nGJiGmEcmlpNBjyHsTwbqpyIEPVpl6br4UcBOYlcI2FJVtJkYhg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.40.0", - "@typescript-eslint/visitor-keys": "5.40.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.40.0.tgz", - "integrity": "sha512-MO0y3T5BQ5+tkkuYZJBjePewsY+cQnfkYeRqS6tPh28niiIwPnQ1t59CSRcs1ZwJJNOdWw7rv9pF8aP58IMihA==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.40.0", - "@typescript-eslint/types": "5.40.0", - "@typescript-eslint/typescript-estree": "5.40.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.40.0.tgz", - "integrity": "sha512-ijJ+6yig+x9XplEpG2K6FUdJeQGGj/15U3S56W9IqXKJqleuD7zJ2AX/miLezwxpd7ZxDAqO87zWufKg+RPZyQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.40.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vime/core": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@vime/core/-/core-5.3.3.tgz", - "integrity": "sha512-bGn6bwiOwI/0d+P+gLH9U3ySK4tar6Qiv7g+RR+c7iuCsl0S16/wP1G677PiglJTvM3wuo2wD3hztMEkiWfvhw==", - "dependencies": { - "@stencil/core": "2.5.2", - "@types/fscreen": "^1.0.1", - "fscreen": "^1.2.0", - "mitt": "^3.0.0", - "stencil-wormhole": "^3.4.1" - } - }, - "node_modules/abab": { - "version": "2.0.5", - "license": "BSD-3-Clause" - }, - "node_modules/acorn": { - "version": "7.4.1", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "license": "MIT", - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-node": { - "version": "1.8.2", - "license": "Apache-2.0", - "dependencies": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/adler-32": { - "version": "1.2.0", - "license": "Apache-2.0", - "dependencies": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.1.0" - }, - "bin": { - "adler32": "bin/adler32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/all-contributors-cli": { - "version": "6.24.0", - "resolved": "https://registry.npmjs.org/all-contributors-cli/-/all-contributors-cli-6.24.0.tgz", - "integrity": "sha512-7oSKr2PnqxsOotuSwciltcFTS1eVRdjR0cn99hbElfff7gRQBShVhsf/XBprY41sLcgqTk0l0MKgKv6QNgZdMg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.7.6", - "async": "^3.1.0", - "chalk": "^4.0.0", - "didyoumean": "^1.2.1", - "inquirer": "^7.3.3", - "json-fixer": "^1.6.8", - "lodash": "^4.11.2", - "node-fetch": "^2.6.0", - "pify": "^5.0.0", - "yargs": "^15.0.1" - }, - "bin": { - "all-contributors": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/all-contributors-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/all-contributors-cli/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/all-contributors-cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/all-contributors-cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/all-contributors-cli/node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/all-contributors-cli/node_modules/inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/all-contributors-cli/node_modules/pify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", - "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/all-contributors-cli/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/all-contributors-cli/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "node_modules/all-contributors-cli/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/all-contributors-cli/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/amdefine": { - "version": "1.0.1", - "license": "BSD-3-Clause OR MIT", - "optional": true, - "engines": { - "node": ">=0.4.2" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ansicolors": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", - "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", - "dev": true - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/argv-formatter": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/array-from": { - "version": "2.1.1", - "license": "MIT" - }, - "node_modules/array-ify": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/array-union": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/arrify": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ast-transform": { - "version": "0.0.0", - "license": "MIT", - "dependencies": { - "escodegen": "~1.2.0", - "esprima": "~1.0.4", - "through": "~2.3.4" - } - }, - "node_modules/ast-transform/node_modules/esprima": { - "version": "1.0.4", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ast-types": { - "version": "0.7.8", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "dev": true - }, - "node_modules/asynckit": { - "version": "0.4.0", - "license": "MIT" - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.12", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.12.tgz", - "integrity": "sha512-WrCGV9/b97Pa+jtwf5UGaRjgQIg7OK3D06GnoYoZNcG1Xb8Gt3EfuKjlhh9i/VtT16g6PYjZ69jdJ2g8FxSC4Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - } - ], - "dependencies": { - "browserslist": "^4.21.4", - "caniuse-lite": "^1.0.30001407", - "fraction.js": "^4.2.0", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", - "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.3.1", - "semver": "^6.1.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", - "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.1", - "core-js-compat": "^3.21.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", - "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-runtime": { - "version": "6.26.0", - "license": "MIT", - "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "node_modules/babel-runtime/node_modules/core-js": { - "version": "2.6.12", - "hasInstallScript": true, - "license": "MIT" - }, - "node_modules/babel-runtime/node_modules/regenerator-runtime": { - "version": "0.11.1", - "license": "MIT" - }, - "node_modules/balanced-match": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.3.1", - "license": "MIT" - }, - "node_modules/before-after-hook": { - "version": "2.2.2", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/bottleneck": { - "version": "2.19.5", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/brfs": { - "version": "2.0.2", - "license": "MIT", - "dependencies": { - "quote-stream": "^1.0.1", - "resolve": "^1.1.5", - "static-module": "^3.0.2", - "through2": "^2.0.0" - }, - "bin": { - "brfs": "bin/cmd.js" - } - }, - "node_modules/brfs/node_modules/through2": { - "version": "2.0.5", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/brotli": { - "version": "1.3.2", - "license": "MIT", - "dependencies": { - "base64-js": "^1.1.2" - } - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "license": "BSD-2-Clause" - }, - "node_modules/browser-resolve": { - "version": "1.11.3", - "license": "MIT", - "dependencies": { - "resolve": "1.1.7" - } - }, - "node_modules/browser-resolve/node_modules/resolve": { - "version": "1.1.7", - "license": "MIT" - }, - "node_modules/browserify-optional": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "ast-transform": "0.0.0", - "ast-types": "^0.7.0", - "browser-resolve": "^1.8.1" - } - }, - "node_modules/browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-equal": { - "version": "0.0.1", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "license": "MIT" - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cacheable-lookup": { - "version": "5.0.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/cacheable-request": { - "version": "7.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cachedir": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", - "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001412", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001412.tgz", - "integrity": "sha512-+TeEIee1gS5bYOiuf+PS/kp2mrXic37Hl66VY6EAfxasIk5fELTktK2oOezYed12H8w7jt3s512PpulQidPjwA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ] - }, - "node_modules/cardinal": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", - "integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=", - "dev": true, - "dependencies": { - "ansicolors": "~0.3.2", - "redeyed": "~2.1.0" - }, - "bin": { - "cdl": "bin/cdl.js" - } - }, - "node_modules/cfb": { - "version": "1.2.1", - "license": "Apache-2.0", - "dependencies": { - "adler-32": "~1.3.0", - "crc-32": "~1.2.0", - "printj": "~1.3.0" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/cfb/node_modules/adler-32": { - "version": "1.3.0", - "license": "Apache-2.0", - "dependencies": { - "printj": "~1.2.2" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/cfb/node_modules/adler-32/node_modules/printj": { - "version": "1.2.3", - "license": "Apache-2.0", - "bin": { - "printj": "bin/printj.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/cfb/node_modules/printj": { - "version": "1.3.0", - "license": "Apache-2.0", - "bin": { - "printj": "bin/printj.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "4.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/chalk/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/chalk/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/chalk/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chardet": { - "version": "0.7.0", - "dev": true, - "license": "MIT" - }, - "node_modules/choices.js": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/choices.js/-/choices.js-10.1.0.tgz", - "integrity": "sha512-NtrFt7c7ZQEGmkWsAV+EHynJhADWoZ82JEfg1+vQ9MMKJD4Ax2rzYPxXe+Q64i0HgUgWG/XTN3gN2pB8UFFFlA==", - "dependencies": { - "deepmerge": "^4.2.2", - "fuse.js": "^6.5.3", - "redux": "^4.1.2" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/ci-info": { - "version": "3.3.0", - "dev": true, - "license": "MIT" - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", - "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-table3": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz", - "integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "colors": "1.4.0" - } - }, - "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-response": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-response": "^1.0.0" - } - }, - "node_modules/codemirror": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", - "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/commands": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/lint": "^6.0.0", - "@codemirror/search": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0" - } - }, - "node_modules/codemirror/node_modules/@codemirror/autocomplete": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.0.2.tgz", - "integrity": "sha512-9PDjnllmXan/7Uax87KGORbxerDJ/cu10SB+n4Jz0zXMEvIh3+TGgZxhIvDOtaQ4jDBQEM7kHYW4vLdQB0DGZQ==", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0" - }, - "peerDependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0" - } - }, - "node_modules/codemirror/node_modules/@codemirror/lint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.0.0.tgz", - "integrity": "sha512-nUUXcJW1Xp54kNs+a1ToPLK8MadO0rMTnJB8Zk4Z8gBdrN0kqV7uvUraU/T2yqg+grDNR38Vmy/MrhQN/RgwiA==", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "crelt": "^1.0.5" - } - }, - "node_modules/codemirror/node_modules/@codemirror/search": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.0.0.tgz", - "integrity": "sha512-rL0rd3AhI0TAsaJPUaEwC63KHLO7KL0Z/dYozXj6E7L3wNHRyx7RfE0/j5HsIf912EE5n2PCb4Vg0rGYmDv4UQ==", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "crelt": "^1.0.5" - } - }, - "node_modules/codepage": { - "version": "1.15.0", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true - }, - "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", - "dev": true - }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.3.0.tgz", - "integrity": "sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw==", - "dev": true, - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/commitizen": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-4.2.5.tgz", - "integrity": "sha512-9sXju8Qrz1B4Tw7kC5KhnvwYQN88qs2zbiB8oyMsnXZyJ24PPGiNM3nHr73d32dnE3i8VJEXddBFIbOgYSEXtQ==", - "dev": true, - "dependencies": { - "cachedir": "2.3.0", - "cz-conventional-changelog": "3.3.0", - "dedent": "0.7.0", - "detect-indent": "6.1.0", - "find-node-modules": "^2.1.2", - "find-root": "1.1.0", - "fs-extra": "9.1.0", - "glob": "7.2.3", - "inquirer": "8.2.4", - "is-utf8": "^0.2.1", - "lodash": "4.17.21", - "minimist": "1.2.6", - "strip-bom": "4.0.0", - "strip-json-comments": "3.1.1" - }, - "bin": { - "commitizen": "bin/commitizen", - "cz": "bin/git-cz", - "git-cz": "bin/git-cz" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/commitizen/node_modules/find-node-modules": { - "version": "2.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "findup-sync": "^4.0.0", - "merge": "^2.1.0" - } - }, - "node_modules/commitizen/node_modules/findup-sync": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^4.0.2", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/commitizen/node_modules/merge": { - "version": "2.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/compare-func": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "engines": [ - "node >= 0.8" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/conventional-changelog-angular": { - "version": "5.0.13", - "dev": true, - "license": "ISC", - "dependencies": { - "compare-func": "^2.0.0", - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-conventionalcommits": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-5.0.0.tgz", - "integrity": "sha512-lCDbA+ZqVFQGUj7h9QBKoIpLhl8iihkO0nCTyRNzuXtcd7ubODpYB04IFy31JloiJgG0Uovu8ot8oxRzn7Nwtw==", - "dev": true, - "dependencies": { - "compare-func": "^2.0.0", - "lodash": "^4.17.15", - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-writer": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "conventional-commits-filter": "^2.0.7", - "dateformat": "^3.0.0", - "handlebars": "^4.7.6", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "semver": "^6.0.0", - "split": "^1.0.0", - "through2": "^4.0.0" - }, - "bin": { - "conventional-changelog-writer": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-commit-types": { - "version": "3.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/conventional-commits-filter": { - "version": "2.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.ismatch": "^4.4.0", - "modify-values": "^1.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-commits-parser": { - "version": "3.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "is-text-path": "^1.0.1", - "JSONStream": "^1.0.4", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, - "bin": { - "conventional-commits-parser": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/convert-source-map": { - "version": "1.7.0", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/core-js": { - "version": "3.6.5", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat": { - "version": "3.23.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.23.3.tgz", - "integrity": "sha512-WSzUs2h2vvmKsacLHNTdpyOC9k43AEhcGoFlVgCY4L7aw98oSBKtPL6vD0/TqZjRWRQYdDSLkzZIni4Crbbiqw==", - "dev": true, - "dependencies": { - "browserslist": "^4.21.0", - "semver": "7.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "license": "MIT" - }, - "node_modules/cosmiconfig": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cosmiconfig-typescript-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.0.0.tgz", - "integrity": "sha512-cVpucSc2Tf+VPwCCR7SZzmQTQkPbkk4O01yXsYqXBIbjE1bhwqSyAgYQkRK1un4i0OPziTleqFhdkmOc4RQ/9g==", - "dev": true, - "engines": { - "node": ">=12", - "npm": ">=6" - }, - "peerDependencies": { - "@types/node": "*", - "cosmiconfig": ">=7", - "ts-node": ">=10", - "typescript": ">=3" - } - }, - "node_modules/cosmiconfig/node_modules/parse-json": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/crc-32": { - "version": "1.2.0", - "license": "Apache-2.0", - "dependencies": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.1.0" - }, - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/crelt": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.5.tgz", - "integrity": "sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==" - }, - "node_modules/cross-env": { - "version": "7.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.1" - }, - "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" - }, - "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-js": { - "version": "4.1.1", - "license": "MIT" - }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/css-blank-pseudo": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", - "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.9" - }, - "bin": { - "css-blank-pseudo": "dist/cli.cjs" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-declaration-sorter": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.0.tgz", - "integrity": "sha512-OGT677UGHJTAVMRhPO+HJ4oKln3wkBTwtDFH0ojbqm+MJm6xuDMHp2nkhh/ThaBqq20IbraBQSWKfSLNHQO9Og==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } - }, - "node_modules/css-functions-list": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.1.0.tgz", - "integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==", - "dev": true, - "engines": { - "node": ">=12.22" - } - }, - "node_modules/css-has-pseudo": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", - "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.9" - }, - "bin": { - "css-has-pseudo": "dist/cli.cjs" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-prefers-color-scheme": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", - "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", - "dev": true, - "bin": { - "css-prefers-color-scheme": "dist/cli.cjs" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-tree": { - "version": "1.1.3", - "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/css-tree/node_modules/source-map": { - "version": "0.6.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cssdb": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.0.1.tgz", - "integrity": "sha512-pT3nzyGM78poCKLAEy2zWIVX2hikq6dIrjuZzLV98MumBg+xMTNYfHx7paUlfiRTgg91O/vR889CIf+qiv79Rw==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano": { - "version": "5.1.13", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.13.tgz", - "integrity": "sha512-S2SL2ekdEz6w6a2epXn4CmMKU4K3KpcyXLKfAYc9UQQqJRkD/2eLUG0vJ3Db/9OvO5GuAdgXw3pFbR6abqghDQ==", - "dev": true, - "dependencies": { - "cssnano-preset-default": "^5.2.12", - "lilconfig": "^2.0.3", - "yaml": "^1.10.2" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/cssnano" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/cssnano-preset-default": { - "version": "5.2.12", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.12.tgz", - "integrity": "sha512-OyCBTZi+PXgylz9HAA5kHyoYhfGcYdwFmyaJzWnzxuGRtnMw/kR6ilW9XzlzlRAtB6PLT/r+prYgkef7hngFew==", - "dev": true, - "dependencies": { - "css-declaration-sorter": "^6.3.0", - "cssnano-utils": "^3.1.0", - "postcss-calc": "^8.2.3", - "postcss-colormin": "^5.3.0", - "postcss-convert-values": "^5.1.2", - "postcss-discard-comments": "^5.1.2", - "postcss-discard-duplicates": "^5.1.0", - "postcss-discard-empty": "^5.1.1", - "postcss-discard-overridden": "^5.1.0", - "postcss-merge-longhand": "^5.1.6", - "postcss-merge-rules": "^5.1.2", - "postcss-minify-font-values": "^5.1.0", - "postcss-minify-gradients": "^5.1.1", - "postcss-minify-params": "^5.1.3", - "postcss-minify-selectors": "^5.2.1", - "postcss-normalize-charset": "^5.1.0", - "postcss-normalize-display-values": "^5.1.0", - "postcss-normalize-positions": "^5.1.1", - "postcss-normalize-repeat-style": "^5.1.1", - "postcss-normalize-string": "^5.1.0", - "postcss-normalize-timing-functions": "^5.1.0", - "postcss-normalize-unicode": "^5.1.0", - "postcss-normalize-url": "^5.1.0", - "postcss-normalize-whitespace": "^5.1.1", - "postcss-ordered-values": "^5.1.3", - "postcss-reduce-initial": "^5.1.0", - "postcss-reduce-transforms": "^5.1.0", - "postcss-svgo": "^5.1.0", - "postcss-unique-selectors": "^5.1.1" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/cssnano-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/csso": { - "version": "4.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "css-tree": "^1.1.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/cssom": { - "version": "0.4.4", - "license": "MIT" - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "license": "MIT", - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "license": "MIT" - }, - "node_modules/cz-conventional-changelog": { - "version": "3.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^2.4.1", - "commitizen": "^4.0.3", - "conventional-commit-types": "^3.0.0", - "lodash.map": "^4.5.1", - "longest": "^2.0.1", - "word-wrap": "^1.0.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@commitlint/load": ">6.1.1" - } - }, - "node_modules/cz-conventional-changelog/node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/d": { - "version": "1.0.1", - "license": "ISC", - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "node_modules/d3-array": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", - "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-force": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-geo": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.0.1.tgz", - "integrity": "sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA==", - "dependencies": { - "d3-array": "2.5.0 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-geo-projection": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz", - "integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==", - "dependencies": { - "commander": "7", - "d3-array": "1 - 3", - "d3-geo": "1.12.0 - 3" - }, - "bin": { - "geo2svg": "bin/geo2svg.js", - "geograticule": "bin/geograticule.js", - "geoproject": "bin/geoproject.js", - "geoquantize": "bin/geoquantize.js", - "geostitch": "bin/geostitch.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-geo-projection/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" - } - }, - "node_modules/dargs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", - "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/dash-ast": { - "version": "1.0.0", - "license": "Apache-2.0" - }, - "node_modules/data-urls": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/dateformat": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decimal.js": { - "version": "10.3.1", - "license": "MIT" - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dedent": { - "version": "0.7.0", - "dev": true, - "license": "MIT" - }, - "node_modules/deep-equal": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.3", - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==", - "dev": true, - "dependencies": { - "clone": "^1.0.2" - } - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==", - "dev": true - }, - "node_modules/del": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", - "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", - "dev": true, - "dependencies": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/deprecation": { - "version": "2.3.1", - "dev": true, - "license": "ISC" - }, - "node_modules/detect-file": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/detective": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", - "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", - "dev": true, - "dependencies": { - "acorn-node": "^1.8.2", - "defined": "^1.0.0", - "minimist": "^1.2.6" - }, - "bin": { - "detective": "bin/detective.js" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/dfa": { - "version": "1.2.0", - "license": "MIT" - }, - "node_modules/didyoumean": { - "version": "1.2.2", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dlv": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/doctrine": { - "version": "3.0.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/domexception": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "license": "BSD-2-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/dot-prop": { - "version": "5.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/duplexer2": { - "version": "0.1.4", - "license": "BSD-3-Clause", - "dependencies": { - "readable-stream": "^2.0.2" - } - }, - "node_modules/ejs": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", - "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==", - "dev": true, - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.264", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.264.tgz", - "integrity": "sha512-AZ6ZRkucHOQT8wke50MktxtmcWZr67kE17X/nAXFf62NIdMdgY6xfsaJD5Szoy84lnkuPWH+4tTNE3s2+bPCiw==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/entities": { - "version": "2.2.0", - "dev": true, - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/env-ci": { - "version": "5.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "fromentries": "^1.3.2", - "java-properties": "^1.0.0" - }, - "engines": { - "node": ">=10.17" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/error-ex/node_modules/is-arrayish": { - "version": "0.2.1", - "dev": true, - "license": "MIT" - }, - "node_modules/es-abstract": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", - "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "regexp.prototype.flags": "^1.4.3", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es5-ext": { - "version": "0.10.53", - "license": "ISC", - "dependencies": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-map": { - "version": "0.1.5", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" - } - }, - "node_modules/es6-set": { - "version": "0.1.5", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" - } - }, - "node_modules/es6-set/node_modules/es6-symbol": { - "version": "3.1.1", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.3", - "license": "ISC", - "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "node_modules/esbuild": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.48.tgz", - "integrity": "sha512-w6N1Yn5MtqK2U1/WZTX9ZqUVb8IOLZkZ5AdHkT6x3cHDMVsYWC7WPdiLmx19w3i4Rwzy5LqsEMtVihG3e4rFzA==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "esbuild-android-64": "0.14.48", - "esbuild-android-arm64": "0.14.48", - "esbuild-darwin-64": "0.14.48", - "esbuild-darwin-arm64": "0.14.48", - "esbuild-freebsd-64": "0.14.48", - "esbuild-freebsd-arm64": "0.14.48", - "esbuild-linux-32": "0.14.48", - "esbuild-linux-64": "0.14.48", - "esbuild-linux-arm": "0.14.48", - "esbuild-linux-arm64": "0.14.48", - "esbuild-linux-mips64le": "0.14.48", - "esbuild-linux-ppc64le": "0.14.48", - "esbuild-linux-riscv64": "0.14.48", - "esbuild-linux-s390x": "0.14.48", - "esbuild-netbsd-64": "0.14.48", - "esbuild-openbsd-64": "0.14.48", - "esbuild-sunos-64": "0.14.48", - "esbuild-windows-32": "0.14.48", - "esbuild-windows-64": "0.14.48", - "esbuild-windows-arm64": "0.14.48" - } - }, - "node_modules/esbuild-android-64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.48.tgz", - "integrity": "sha512-3aMjboap/kqwCUpGWIjsk20TtxVoKck8/4Tu19rubh7t5Ra0Yrpg30Mt1QXXlipOazrEceGeWurXKeFJgkPOUg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-android-arm64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.48.tgz", - "integrity": "sha512-vptI3K0wGALiDq+EvRuZotZrJqkYkN5282iAfcffjI5lmGG9G1ta/CIVauhY42MBXwEgDJkweiDcDMRLzBZC4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.48.tgz", - "integrity": "sha512-gGQZa4+hab2Va/Zww94YbshLuWteyKGD3+EsVon8EWTWhnHFRm5N9NbALNbwi/7hQ/hM1Zm4FuHg+k6BLsl5UA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-arm64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.48.tgz", - "integrity": "sha512-bFjnNEXjhZT+IZ8RvRGNJthLWNHV5JkCtuOFOnjvo5pC0sk2/QVk0Qc06g2PV3J0TcU6kaPC3RN9yy9w2PSLEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.48.tgz", - "integrity": "sha512-1NOlwRxmOsnPcWOGTB10JKAkYSb2nue0oM1AfHWunW/mv3wERfJmnYlGzL3UAOIUXZqW8GeA2mv+QGwq7DToqA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-arm64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.48.tgz", - "integrity": "sha512-gXqKdO8wabVcYtluAbikDH2jhXp+Klq5oCD5qbVyUG6tFiGhrC9oczKq3vIrrtwcxDQqK6+HDYK8Zrd4bCA9Gw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-32": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.48.tgz", - "integrity": "sha512-ghGyDfS289z/LReZQUuuKq9KlTiTspxL8SITBFQFAFRA/IkIvDpnZnCAKTCjGXAmUqroMQfKJXMxyjJA69c/nQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.48.tgz", - "integrity": "sha512-vni3p/gppLMVZLghI7oMqbOZdGmLbbKR23XFARKnszCIBpEMEDxOMNIKPmMItQrmH/iJrL1z8Jt2nynY0bE1ug==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.48.tgz", - "integrity": "sha512-+VfSV7Akh1XUiDNXgqgY1cUP1i2vjI+BmlyXRfVz5AfV3jbpde8JTs5Q9sYgaoq5cWfuKfoZB/QkGOI+QcL1Tw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.48.tgz", - "integrity": "sha512-3CFsOlpoxlKPRevEHq8aAntgYGYkE1N9yRYAcPyng/p4Wyx0tPR5SBYsxLKcgPB9mR8chHEhtWYz6EZ+H199Zw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-mips64le": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.48.tgz", - "integrity": "sha512-cs0uOiRlPp6ymknDnjajCgvDMSsLw5mST2UXh+ZIrXTj2Ifyf2aAP3Iw4DiqgnyYLV2O/v/yWBJx+WfmKEpNLA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-ppc64le": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.48.tgz", - "integrity": "sha512-+2F0vJMkuI0Wie/wcSPDCqXvSFEELH7Jubxb7mpWrA/4NpT+/byjxDz0gG6R1WJoeDefcrMfpBx4GFNN1JQorQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-riscv64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.48.tgz", - "integrity": "sha512-BmaK/GfEE+5F2/QDrIXteFGKnVHGxlnK9MjdVKMTfvtmudjY3k2t8NtlY4qemKSizc+QwyombGWTBDc76rxePA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-s390x": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.48.tgz", - "integrity": "sha512-tndw/0B9jiCL+KWKo0TSMaUm5UWBLsfCKVdbfMlb3d5LeV9WbijZ8Ordia8SAYv38VSJWOEt6eDCdOx8LqkC4g==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-netbsd-64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.48.tgz", - "integrity": "sha512-V9hgXfwf/T901Lr1wkOfoevtyNkrxmMcRHyticybBUHookznipMOHoF41Al68QBsqBxnITCEpjjd4yAos7z9Tw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-openbsd-64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.48.tgz", - "integrity": "sha512-+IHf4JcbnnBl4T52egorXMatil/za0awqzg2Vy6FBgPcBpisDWT2sVz/tNdrK9kAqj+GZG/jZdrOkj7wsrNTKA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-sunos-64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.48.tgz", - "integrity": "sha512-77m8bsr5wOpOWbGi9KSqDphcq6dFeJyun8TA+12JW/GAjyfTwVtOnN8DOt6DSPUfEV+ltVMNqtXUeTeMAxl5KA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-32": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.48.tgz", - "integrity": "sha512-EPgRuTPP8vK9maxpTGDe5lSoIBHGKO/AuxDncg5O3NkrPeLNdvvK8oywB0zGaAZXxYWfNNSHskvvDgmfVTguhg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.48.tgz", - "integrity": "sha512-YmpXjdT1q0b8ictSdGwH3M8VCoqPpK1/UArze3X199w6u8hUx3V8BhAi1WjbsfDYRBanVVtduAhh2sirImtAvA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-arm64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.48.tgz", - "integrity": "sha512-HHaOMCsCXp0rz5BT2crTka6MPWVno121NKApsGs/OIW5QC0ggC69YMGs1aJct9/9FSUF4A1xNE/cLvgB5svR4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "1.2.0", - "dependencies": { - "esprima": "~1.0.4", - "estraverse": "~1.5.0", - "esutils": "~1.0.0" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=0.4.0" - }, - "optionalDependencies": { - "source-map": "~0.1.30" - } - }, - "node_modules/escodegen/node_modules/esprima": { - "version": "1.0.4", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "1.5.1", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/escodegen/node_modules/esutils": { - "version": "1.0.0", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.1.43", - "optional": true, - "dependencies": { - "amdefine": ">=0.0.4" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.25.0.tgz", - "integrity": "sha512-DVlJOZ4Pn50zcKW5bYH7GQK/9MsoQG2d5eDH0ebEkE8PbgzTTmtt/VTH9GGJ4BfeZCpBLqFfvsjX35UacUL83A==", - "dev": true, - "dependencies": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.10.5", - "@humanwhocodes/module-importer": "^1.0.1", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "globby": "^11.1.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", - "dev": true, - "dependencies": { - "prettier-linter-helpers": "^1.0.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "eslint": ">=7.28.0", - "prettier": ">=2.0.0" - }, - "peerDependenciesMeta": { - "eslint-config-prettier": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", - "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", - "dev": true, - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.2.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-is-function": { - "version": "1.0.0", - "license": "Apache-2.0" - }, - "node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true - }, - "node_modules/esutils": { - "version": "2.0.3", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/event-emitter": { - "version": "0.3.5", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/get-stream": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/execa/node_modules/human-signals": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/exit-on-epipe": { - "version": "1.0.1", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ext": { - "version": "1.5.0", - "license": "ISC", - "dependencies": { - "type": "^2.5.0" - } - }, - "node_modules/ext/node_modules/type": { - "version": "2.5.0", - "license": "ISC" - }, - "node_modules/external-editor": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-diff": { - "version": "1.2.0", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "license": "MIT" - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fastq": { - "version": "1.8.0", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fflate": { - "version": "0.3.11", - "license": "MIT" - }, - "node_modules/figures": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-root": { - "version": "1.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-versions": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "semver-regex": "^3.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatpickr": { - "version": "4.6.13", - "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz", - "integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==" - }, - "node_modules/flatted": { - "version": "3.1.1", - "dev": true, - "license": "ISC" - }, - "node_modules/fontkit": { - "version": "1.8.1", - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.26.0", - "brfs": "^2.0.0", - "brotli": "^1.2.0", - "browserify-optional": "^1.0.1", - "clone": "^1.0.4", - "deep-equal": "^1.0.0", - "dfa": "^1.2.0", - "restructure": "^0.5.3", - "tiny-inflate": "^1.0.2", - "unicode-properties": "^1.2.2", - "unicode-trie": "^0.3.0" - } - }, - "node_modules/fontkit/node_modules/unicode-trie": { - "version": "0.3.1", - "license": "MIT", - "dependencies": { - "pako": "^0.2.5", - "tiny-inflate": "^1.0.0" - } - }, - "node_modules/form-data": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/frac": { - "version": "1.1.2", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/fraction.js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", - "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", - "dev": true, - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://www.patreon.com/infusion" - } - }, - "node_modules/from2": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "node_modules/fromentries": { - "version": "1.3.2", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fs-extra/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/fscreen": { - "version": "1.2.0", - "license": "MIT" - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "license": "MIT" - }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/fuse.js": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.5.3.tgz", - "integrity": "sha512-sA5etGE7yD/pOqivZRBvUBd/NaL2sjAu6QuSaFoe1H2BrJSkH/T/UXAJ8CdXdw7DvY3Hs8CXKYkDWX7RiP5KOg==", - "engines": { - "node": ">=10" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-assigned-identifiers": { - "version": "1.2.0", - "license": "Apache-2.0" - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "dev": true - }, - "node_modules/get-stream": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/git-log-parser": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "argv-formatter": "~1.0.0", - "spawn-error-forwarder": "~1.0.0", - "split2": "~1.0.0", - "stream-combiner2": "~1.1.1", - "through2": "~2.0.0", - "traverse": "~0.6.6" - } - }, - "node_modules/git-log-parser/node_modules/split2": { - "version": "1.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "through2": "~2.0.0" - } - }, - "node_modules/git-log-parser/node_modules/through2": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/git-raw-commits": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", - "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", - "dev": true, - "dependencies": { - "dargs": "^7.0.0", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, - "bin": { - "git-raw-commits": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/global-dirs": { - "version": "0.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "^1.3.4" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/global-modules": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-prefix": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globals/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globjoin": { - "version": "0.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/got": { - "version": "11.8.5", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", - "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.4", - "dev": true, - "license": "ISC" - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "node_modules/handlebars": { - "version": "4.7.7", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/handlebars/node_modules/source-map": { - "version": "0.6.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hook-std": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hosted-git-info": { - "version": "4.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hpagent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.0.0.tgz", - "integrity": "sha512-SCleE2Uc1bM752ymxg8QXYGW0TWtAV4ZW3TqH1aOnyi6T6YW2xadCcclm5qeVjvMvfQ2RKNtZxO7uVb9CTPt1A==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/html-tags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", - "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.1.0", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "license": "MIT", - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http2-wrapper": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/http2-wrapper/node_modules/quick-lru": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", - "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", - "dev": true, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/husky": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz", - "integrity": "sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==", - "dev": true, - "bin": { - "husky": "lib/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/idb": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.2.tgz", - "integrity": "sha512-jjKrT1EnyZewQ/gCBb/eyiYrhGzws2FeY92Yx8qT9S9GeQAmo4JFVIiWRIfKW/6Ob9A+UDAOW9j9jn58fy2HIg==", - "dev": true - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/import-lazy": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "dev": true, - "license": "ISC" - }, - "node_modules/inquirer": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", - "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/inquirer/node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inquirer/node_modules/rxjs": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", - "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/inquirer/node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true - }, - "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/into-stream": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "from2": "^2.3.0", - "p-is-promise": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-arguments": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-ci": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.2", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "license": "MIT" - }, - "node_modules/is-regex": { - "version": "1.1.4", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-text-path": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "text-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-utf8": { - "version": "0.2.1", - "dev": true, - "license": "MIT" - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/issue-parser": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.capitalize": "^4.2.1", - "lodash.escaperegexp": "^4.1.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.uniqby": "^4.7.0" - }, - "engines": { - "node": ">=10.13" - } - }, - "node_modules/jake": { - "version": "10.8.5", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", - "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", - "dev": true, - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/java-properties": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/js-sdsl": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", - "integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==", - "dev": true - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdom": { - "version": "16.7.0", - "license": "MIT", - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/acorn": { - "version": "8.5.0", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/jsdom/node_modules/escodegen": { - "version": "2.0.0", - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/jsdom/node_modules/estraverse": { - "version": "5.2.0", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/jsdom/node_modules/levn": { - "version": "0.3.0", - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/jsdom/node_modules/optionator": { - "version": "0.8.3", - "license": "MIT", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/jsdom/node_modules/prelude-ls": { - "version": "1.1.2", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/jsdom/node_modules/source-map": { - "version": "0.6.1", - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jsdom/node_modules/type-check": { - "version": "0.3.2", - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-fixer": { - "version": "1.6.13", - "resolved": "https://registry.npmjs.org/json-fixer/-/json-fixer-1.6.13.tgz", - "integrity": "sha512-DKQ71M+0uwAG3QsUkeVgh6XREw/OkpnTfHfM+sdmxRjHvYZ8PlcMVF4ibsHQ1ckR63NROs68qUr1I0u6yPVePQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.14.6", - "chalk": "^4.1.2", - "pegjs": "^0.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.0", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "dev": true, - "license": "ISC" - }, - "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonfile/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/jsonparse": { - "version": "1.3.1", - "dev": true, - "engines": [ - "node >= 0.2.0" - ], - "license": "MIT" - }, - "node_modules/jsonpointer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz", - "integrity": "sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/JSONStream": { - "version": "1.3.5", - "dev": true, - "license": "(MIT OR Apache-2.0)", - "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/keyv": { - "version": "4.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/known-css-properties": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.25.0.tgz", - "integrity": "sha512-b0/9J1O9Jcyik1GC6KC42hJ41jKwdO/Mq8Mdo5sYN+IuRTXs2YFHZC3kZSx6ueusqa95x3wLYe/ytKjbAfGixA==", - "dev": true - }, - "node_modules/leaflet": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.2.tgz", - "integrity": "sha512-Kc77HQvWO+y9y2oIs3dn5h5sy2kr3j41ewdqCMEUA4N89lgfUUfOBy7wnnHEstDpefiGFObq12FdopGRMx4J7g==" - }, - "node_modules/leaflet.markercluster": { - "version": "1.5.3", - "license": "MIT", - "peerDependencies": { - "leaflet": "^1.3.1" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lilconfig": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.5.tgz", - "integrity": "sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/linebreak": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "base64-js": "0.0.8", - "brfs": "^2.0.2", - "unicode-trie": "^1.0.0" - } - }, - "node_modules/linebreak/node_modules/base64-js": { - "version": "0.0.8", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/linebreak/node_modules/unicode-trie": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "pako": "^0.2.5", - "tiny-inflate": "^1.0.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.1.6", - "dev": true, - "license": "MIT" - }, - "node_modules/lint-staged": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.0.3.tgz", - "integrity": "sha512-9hmrwSCFroTSYLjflGI8Uk+GWAwMB4OlpU4bMJEAT5d/llQwtYKoim4bLOyLCuWFAhWEupE0vkIFqtw/WIsPug==", - "dev": true, - "dependencies": { - "cli-truncate": "^3.1.0", - "colorette": "^2.0.17", - "commander": "^9.3.0", - "debug": "^4.3.4", - "execa": "^6.1.0", - "lilconfig": "2.0.5", - "listr2": "^4.0.5", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-inspect": "^1.12.2", - "pidtree": "^0.6.0", - "string-argv": "^0.3.1", - "yaml": "^2.1.1" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" - }, - "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/lint-staged" - } - }, - "node_modules/lint-staged/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/ansi-styles": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz", - "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", - "dev": true, - "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/lint-staged/node_modules/execa": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", - "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^3.0.1", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/string-width": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.0.1.tgz", - "integrity": "sha512-5ohWO/M4//8lErlUUtrFy3b11GtNOuMOU0ysKCDXFcfXuuvUXu95akgj/i8ofmaGdN0hCqyl6uu9i8dS/mQp5g==", - "dev": true, - "dependencies": { - "emoji-regex": "^9.2.2", - "is-fullwidth-code-point": "^4.0.0", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/yaml": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz", - "integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/listr2": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.5.tgz", - "integrity": "sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==", - "dev": true, - "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.16", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.5.5", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } - } - }, - "node_modules/listr2/node_modules/rxjs": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", - "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/listr2/node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true - }, - "node_modules/lit": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/lit/-/lit-2.4.0.tgz", - "integrity": "sha512-fdgzxEtLrZFQU/BqTtxFQCLwlZd9bdat+ltzSFjvWkZrs7eBmeX0L5MHUMb3kYIkuS8Xlfnii/iI5klirF8/Xg==", - "dependencies": { - "@lit/reactive-element": "^1.4.0", - "lit-element": "^3.2.0", - "lit-html": "^2.4.0" - } - }, - "node_modules/lit-element": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.2.0.tgz", - "integrity": "sha512-HbE7yt2SnUtg5DCrWt028oaU4D5F4k/1cntAFHTkzY8ZIa8N0Wmu92PxSxucsQSOXlODFrICkQ5x/tEshKi13g==", - "dependencies": { - "@lit/reactive-element": "^1.3.0", - "lit-html": "^2.2.0" - } - }, - "node_modules/lit-html": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.4.0.tgz", - "integrity": "sha512-G6qXu4JNUpY6aaF2VMfaszhO9hlWw0hOTRFDmuMheg/nDYGB+2RztUSOyrzALAbr8Nh0Y7qjhYkReh3rPnplVg==", - "dependencies": { - "@types/trusted-types": "^2.0.2" - } - }, - "node_modules/load-json-file": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/node_modules/pify": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/node_modules/strip-bom": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "license": "MIT" - }, - "node_modules/lodash.capitalize": { - "version": "4.2.1", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.castarray": { - "version": "4.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "node_modules/lodash.escaperegexp": { - "version": "4.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.ismatch": { - "version": "4.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.map": { - "version": "4.6.0", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "dev": true - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "dev": true - }, - "node_modules/lodash.uniqby": { - "version": "4.7.0", - "dev": true, - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-update/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-update/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/longest": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lowercase-keys": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/magic-string": { - "version": "0.25.1", - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.1" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/map-obj": { - "version": "4.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/marked": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.1.tgz", - "integrity": "sha512-0cNMnTcUJPxbA6uWmCmjWz4NJRe/0Xfk2NhXCUHjew9qJzFN20krFnsUe7QynwqOwa5m1fZ4UDg0ycKFVC0ccw==", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/marked-terminal": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.0.0.tgz", - "integrity": "sha512-26604GmGmW63ElxcXpE2xfMdbtgD/qiwIqOh/+5+uPe6NVU4bU433+wvPTfq6NZcGr16KWqwu/dzsKxg3IL2Xw==", - "dev": true, - "dependencies": { - "ansi-escapes": "^5.0.0", - "cardinal": "^2.1.1", - "chalk": "^5.0.0", - "cli-table3": "^0.6.0", - "node-emoji": "^1.11.0", - "supports-hyperlinks": "^2.2.0" - }, - "engines": { - "node": " >=14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "marked": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" - } - }, - "node_modules/marked-terminal/node_modules/ansi-escapes": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", - "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", - "dev": true, - "dependencies": { - "type-fest": "^1.0.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/marked-terminal/node_modules/chalk": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.0.tgz", - "integrity": "sha512-/duVOqst+luxCQRKEo4bNxinsOQtMP80ZYm7mMqzuh5PociNL0PvmHFvREJ9ueYL2TxlHjBcmLCdmocx9Vg+IQ==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/marked-terminal/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mathml-tag-names": { - "version": "2.1.3", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/mdn-data": { - "version": "2.0.14", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/meow": { - "version": "8.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-source-map": { - "version": "1.0.4", - "license": "MIT", - "dependencies": { - "source-map": "^0.5.6" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.45.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.28", - "license": "MIT", - "dependencies": { - "mime-db": "1.45.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-response": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/mini-svg-data-uri": { - "version": "1.2.3", - "dev": true, - "license": "MIT" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "node_modules/minimist-options": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/mitt": { - "version": "3.0.0", - "license": "MIT" - }, - "node_modules/modify-values": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "license": "MIT" - }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/neo-async": { - "version": "2.6.2", - "dev": true, - "license": "MIT" - }, - "node_modules/nerf-dart": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", - "integrity": "sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo=", - "dev": true - }, - "node_modules/next-tick": { - "version": "1.0.0", - "license": "MIT" - }, - "node_modules/node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "dev": true, - "dependencies": { - "lodash": "^4.17.21" - } - }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "dev": true - }, - "node_modules/normalize-package-data": { - "version": "3.0.2", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^4.0.1", - "resolve": "^1.20.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.3.5", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "6.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm": { - "version": "8.13.2", - "resolved": "https://registry.npmjs.org/npm/-/npm-8.13.2.tgz", - "integrity": "sha512-aS6q/QKxkw9mTX8gR7Ft38BcRkW1i+h3sI1yAFmfQ30Yl1a1G4ZX3oNGDzaLCilU5ThFZQBS1F4ZSZsrVxJ7HA==", - "bundleDependencies": [ - "@isaacs/string-locale-compare", - "@npmcli/arborist", - "@npmcli/ci-detect", - "@npmcli/config", - "@npmcli/fs", - "@npmcli/map-workspaces", - "@npmcli/package-json", - "@npmcli/run-script", - "abbrev", - "archy", - "cacache", - "chalk", - "chownr", - "cli-columns", - "cli-table3", - "columnify", - "fastest-levenshtein", - "glob", - "graceful-fs", - "hosted-git-info", - "ini", - "init-package-json", - "is-cidr", - "json-parse-even-better-errors", - "libnpmaccess", - "libnpmdiff", - "libnpmexec", - "libnpmfund", - "libnpmhook", - "libnpmorg", - "libnpmpack", - "libnpmpublish", - "libnpmsearch", - "libnpmteam", - "libnpmversion", - "make-fetch-happen", - "minipass", - "minipass-pipeline", - "mkdirp", - "mkdirp-infer-owner", - "ms", - "node-gyp", - "nopt", - "npm-audit-report", - "npm-install-checks", - "npm-package-arg", - "npm-pick-manifest", - "npm-profile", - "npm-registry-fetch", - "npm-user-validate", - "npmlog", - "opener", - "pacote", - "parse-conflict-json", - "proc-log", - "qrcode-terminal", - "read", - "read-package-json", - "read-package-json-fast", - "readdir-scoped-modules", - "rimraf", - "semver", - "ssri", - "tar", - "text-table", - "tiny-relative-date", - "treeverse", - "validate-npm-package-name", - "which", - "write-file-atomic" - ], - "dev": true, - "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^5.0.4", - "@npmcli/ci-detect": "^2.0.0", - "@npmcli/config": "^4.1.0", - "@npmcli/fs": "^2.1.0", - "@npmcli/map-workspaces": "^2.0.3", - "@npmcli/package-json": "^2.0.0", - "@npmcli/run-script": "^4.1.5", - "abbrev": "~1.1.1", - "archy": "~1.0.0", - "cacache": "^16.1.1", - "chalk": "^4.1.2", - "chownr": "^2.0.0", - "cli-columns": "^4.0.0", - "cli-table3": "^0.6.2", - "columnify": "^1.6.0", - "fastest-levenshtein": "^1.0.12", - "glob": "^8.0.1", - "graceful-fs": "^4.2.10", - "hosted-git-info": "^5.0.0", - "ini": "^3.0.0", - "init-package-json": "^3.0.2", - "is-cidr": "^4.0.2", - "json-parse-even-better-errors": "^2.3.1", - "libnpmaccess": "^6.0.2", - "libnpmdiff": "^4.0.2", - "libnpmexec": "^4.0.2", - "libnpmfund": "^3.0.1", - "libnpmhook": "^8.0.2", - "libnpmorg": "^4.0.2", - "libnpmpack": "^4.0.2", - "libnpmpublish": "^6.0.2", - "libnpmsearch": "^5.0.2", - "libnpmteam": "^4.0.2", - "libnpmversion": "^3.0.1", - "make-fetch-happen": "^10.1.8", - "minipass": "^3.1.6", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "mkdirp-infer-owner": "^2.0.0", - "ms": "^2.1.2", - "node-gyp": "^9.0.0", - "nopt": "^5.0.0", - "npm-audit-report": "^3.0.0", - "npm-install-checks": "^5.0.0", - "npm-package-arg": "^9.0.2", - "npm-pick-manifest": "^7.0.1", - "npm-profile": "^6.1.0", - "npm-registry-fetch": "^13.1.1", - "npm-user-validate": "^1.0.1", - "npmlog": "^6.0.2", - "opener": "^1.5.2", - "pacote": "^13.6.1", - "parse-conflict-json": "^2.0.2", - "proc-log": "^2.0.1", - "qrcode-terminal": "^0.12.0", - "read": "~1.0.7", - "read-package-json": "^5.0.1", - "read-package-json-fast": "^2.0.3", - "readdir-scoped-modules": "^1.1.0", - "rimraf": "^3.0.2", - "semver": "^7.3.7", - "ssri": "^9.0.1", - "tar": "^6.1.11", - "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", - "treeverse": "^2.0.0", - "validate-npm-package-name": "^4.0.0", - "which": "^2.0.2", - "write-file-atomic": "^4.0.1" - }, - "bin": { - "npm": "bin/npm-cli.js", - "npx": "bin/npx-cli.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/@colors/colors": { - "version": "1.5.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/npm/node_modules/@gar/promisify": { - "version": "1.1.3", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/@isaacs/string-locale-compare": { - "version": "1.1.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "5.2.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/installed-package-contents": "^1.0.7", - "@npmcli/map-workspaces": "^2.0.3", - "@npmcli/metavuln-calculator": "^3.0.1", - "@npmcli/move-file": "^2.0.0", - "@npmcli/name-from-folder": "^1.0.1", - "@npmcli/node-gyp": "^2.0.0", - "@npmcli/package-json": "^2.0.0", - "@npmcli/run-script": "^4.1.3", - "bin-links": "^3.0.0", - "cacache": "^16.0.6", - "common-ancestor-path": "^1.0.1", - "json-parse-even-better-errors": "^2.3.1", - "json-stringify-nice": "^1.1.4", - "mkdirp": "^1.0.4", - "mkdirp-infer-owner": "^2.0.0", - "nopt": "^5.0.0", - "npm-install-checks": "^5.0.0", - "npm-package-arg": "^9.0.0", - "npm-pick-manifest": "^7.0.0", - "npm-registry-fetch": "^13.0.0", - "npmlog": "^6.0.2", - "pacote": "^13.6.1", - "parse-conflict-json": "^2.0.1", - "proc-log": "^2.0.0", - "promise-all-reject-late": "^1.0.0", - "promise-call-limit": "^1.0.1", - "read-package-json-fast": "^2.0.2", - "readdir-scoped-modules": "^1.1.0", - "rimraf": "^3.0.2", - "semver": "^7.3.7", - "ssri": "^9.0.0", - "treeverse": "^2.0.0", - "walk-up-path": "^1.0.0" - }, - "bin": { - "arborist": "bin/index.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/@npmcli/ci-detect": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" - } - }, - "node_modules/npm/node_modules/@npmcli/config": { - "version": "4.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/map-workspaces": "^2.0.2", - "ini": "^3.0.0", - "mkdirp-infer-owner": "^2.0.0", - "nopt": "^5.0.0", - "proc-log": "^2.0.0", - "read-package-json-fast": "^2.0.3", - "semver": "^7.3.5", - "walk-up-path": "^1.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/@npmcli/disparity-colors": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "ansi-styles": "^4.3.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/@npmcli/fs": { - "version": "2.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/@npmcli/git": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/promise-spawn": "^3.0.0", - "lru-cache": "^7.4.4", - "mkdirp": "^1.0.4", - "npm-pick-manifest": "^7.0.0", - "proc-log": "^2.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^2.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/@npmcli/installed-package-contents": { - "version": "1.0.7", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1" - }, - "bin": { - "installed-package-contents": "index.js" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/npm/node_modules/@npmcli/map-workspaces": { - "version": "2.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/name-from-folder": "^1.0.1", - "glob": "^8.0.1", - "minimatch": "^5.0.1", - "read-package-json-fast": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { - "version": "3.1.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "cacache": "^16.0.0", - "json-parse-even-better-errors": "^2.3.1", - "pacote": "^13.0.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/@npmcli/move-file": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/@npmcli/name-from-folder": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/@npmcli/node-gyp": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^2.3.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "infer-owner": "^1.0.4" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "4.1.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/node-gyp": "^2.0.0", - "@npmcli/promise-spawn": "^3.0.0", - "node-gyp": "^9.0.0", - "read-package-json-fast": "^2.0.3", - "which": "^2.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/@tootallnate/once": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/npm/node_modules/abbrev": { - "version": "1.1.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/agent-base": { - "version": "6.0.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/npm/node_modules/agentkeepalive": { - "version": "4.2.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "depd": "^1.1.2", - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/npm/node_modules/aggregate-error": { - "version": "3.1.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/npm/node_modules/aproba": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/archy": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/are-we-there-yet": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" - } - }, - "node_modules/npm/node_modules/asap": { - "version": "2.0.6", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/bin-links": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "cmd-shim": "^5.0.0", - "mkdirp-infer-owner": "^2.0.0", - "npm-normalize-package-bin": "^1.0.0", - "read-cmd-shim": "^3.0.0", - "rimraf": "^3.0.0", - "write-file-atomic": "^4.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/binary-extensions": { - "version": "2.2.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/npm/node_modules/builtins": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "semver": "^7.0.0" - } - }, - "node_modules/npm/node_modules/cacache": { - "version": "16.1.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^1.1.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/npm/node_modules/chownr": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/cidr-regex": { - "version": "3.1.1", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "ip-regex": "^4.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/clean-stack": { - "version": "2.2.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/cli-columns": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/npm/node_modules/cli-table3": { - "version": "0.6.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/npm/node_modules/clone": { - "version": "1.0.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/npm/node_modules/cmd-shim": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "mkdirp-infer-owner": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/npm/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/color-support": { - "version": "1.1.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/npm/node_modules/columnify": { - "version": "1.6.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "strip-ansi": "^6.0.1", - "wcwidth": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/npm/node_modules/common-ancestor-path": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/concat-map": { - "version": "0.0.1", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/console-control-strings": { - "version": "1.1.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/debug": { - "version": "4.3.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/npm/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/debuglog": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/npm/node_modules/defaults": { - "version": "1.0.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - } - }, - "node_modules/npm/node_modules/delegates": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/depd": { - "version": "1.1.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/npm/node_modules/dezalgo": { - "version": "1.0.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "node_modules/npm/node_modules/diff": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/npm/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/encoding": { - "version": "0.1.13", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/npm/node_modules/env-paths": { - "version": "2.2.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/err-code": { - "version": "2.0.3", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/fastest-levenshtein": { - "version": "1.0.12", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/fs-minipass": { - "version": "2.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/fs.realpath": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/function-bind": { - "version": "1.1.1", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/gauge": { - "version": "4.0.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/glob": { - "version": "8.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/graceful-fs": { - "version": "4.2.10", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/has": { - "version": "1.0.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/npm/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/has-unicode": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/hosted-git-info": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^7.5.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" - } - }, - "node_modules/npm/node_modules/http-cache-semantics": { - "version": "4.1.0", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause" - }, - "node_modules/npm/node_modules/http-proxy-agent": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/npm/node_modules/https-proxy-agent": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/npm/node_modules/humanize-ms": { - "version": "1.2.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/npm/node_modules/iconv-lite": { - "version": "0.6.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/ignore-walk": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minimatch": "^5.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/imurmurhash": { - "version": "0.1.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/npm/node_modules/indent-string": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/infer-owner": { - "version": "1.0.4", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/inflight": { - "version": "1.0.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/npm/node_modules/inherits": { - "version": "2.0.4", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/ini": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/init-package-json": { - "version": "3.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-package-arg": "^9.0.1", - "promzard": "^0.3.0", - "read": "^1.0.7", - "read-package-json": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "^4.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/ip": { - "version": "1.1.8", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/ip-regex": { - "version": "4.3.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/is-cidr": { - "version": "4.0.2", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "cidr-regex": "^3.1.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/is-core-module": { - "version": "2.9.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/npm/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/is-lambda": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/json-stringify-nice": { - "version": "1.1.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/jsonparse": { - "version": "1.3.1", - "dev": true, - "engines": [ - "node >= 0.2.0" - ], - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/just-diff": { - "version": "5.0.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/just-diff-apply": { - "version": "5.2.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/libnpmaccess": { - "version": "6.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "minipass": "^3.1.1", - "npm-package-arg": "^9.0.1", - "npm-registry-fetch": "^13.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/libnpmdiff": { - "version": "4.0.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/disparity-colors": "^2.0.0", - "@npmcli/installed-package-contents": "^1.0.7", - "binary-extensions": "^2.2.0", - "diff": "^5.0.0", - "minimatch": "^5.0.1", - "npm-package-arg": "^9.0.1", - "pacote": "^13.6.1", - "tar": "^6.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/libnpmexec": { - "version": "4.0.8", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^5.0.0", - "@npmcli/ci-detect": "^2.0.0", - "@npmcli/run-script": "^4.1.3", - "chalk": "^4.1.0", - "mkdirp-infer-owner": "^2.0.0", - "npm-package-arg": "^9.0.1", - "npmlog": "^6.0.2", - "pacote": "^13.6.1", - "proc-log": "^2.0.0", - "read": "^1.0.7", - "read-package-json-fast": "^2.0.2", - "walk-up-path": "^1.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/libnpmfund": { - "version": "3.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^5.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/libnpmhook": { - "version": "8.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^13.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/libnpmorg": { - "version": "4.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^13.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/libnpmpack": { - "version": "4.1.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/run-script": "^4.1.3", - "npm-package-arg": "^9.0.1", - "pacote": "^13.6.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/libnpmpublish": { - "version": "6.0.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "normalize-package-data": "^4.0.0", - "npm-package-arg": "^9.0.1", - "npm-registry-fetch": "^13.0.0", - "semver": "^7.3.7", - "ssri": "^9.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/libnpmsearch": { - "version": "5.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-registry-fetch": "^13.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/libnpmteam": { - "version": "4.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^13.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/libnpmversion": { - "version": "3.0.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^3.0.0", - "@npmcli/run-script": "^4.1.3", - "json-parse-even-better-errors": "^2.3.1", - "proc-log": "^2.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/lru-cache": { - "version": "7.9.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/npm/node_modules/make-fetch-happen": { - "version": "10.1.8", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/minimatch": { - "version": "5.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/minipass": { - "version": "3.1.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minipass-collect": { - "version": "1.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/minipass-fetch": { - "version": "2.1.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.1.6", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/npm/node_modules/minipass-flush": { - "version": "1.0.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/minipass-json-stream": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, - "node_modules/npm/node_modules/minipass-pipeline": { - "version": "1.2.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minipass-sized": { - "version": "1.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minizlib": { - "version": "2.1.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/mkdirp": { - "version": "1.0.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/mkdirp-infer-owner": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "infer-owner": "^1.0.4", - "mkdirp": "^1.0.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/mute-stream": { - "version": "0.0.8", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/negotiator": { - "version": "0.6.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/npm/node_modules/node-gyp": { - "version": "9.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^10.0.3", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^12.22 || ^14.13 || >=16" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/brace-expansion": { - "version": "1.1.11", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npm/node_modules/nopt": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/normalize-package-data": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^5.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" - } - }, - "node_modules/npm/node_modules/npm-audit-report": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "chalk": "^4.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/npm-bundled": { - "version": "1.1.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "node_modules/npm/node_modules/npm-install-checks": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "semver": "^7.1.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/npm-normalize-package-bin": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/npm-package-arg": { - "version": "9.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^4.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/npm-packlist": { - "version": "5.1.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^8.0.1", - "ignore-walk": "^5.0.1", - "npm-bundled": "^1.1.2", - "npm-normalize-package-bin": "^1.0.1" - }, - "bin": { - "npm-packlist": "bin/index.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/npm-pick-manifest": { - "version": "7.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-install-checks": "^5.0.0", - "npm-normalize-package-bin": "^1.0.1", - "npm-package-arg": "^9.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/npm-profile": { - "version": "6.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-registry-fetch": "^13.0.1", - "proc-log": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/npm-registry-fetch": { - "version": "13.1.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "make-fetch-happen": "^10.0.6", - "minipass": "^3.1.6", - "minipass-fetch": "^2.0.3", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.1.2", - "npm-package-arg": "^9.0.1", - "proc-log": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/npm-user-validate": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause" - }, - "node_modules/npm/node_modules/npmlog": { - "version": "6.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/once": { - "version": "1.4.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/npm/node_modules/opener": { - "version": "1.5.2", - "dev": true, - "inBundle": true, - "license": "(WTFPL OR MIT)", - "bin": { - "opener": "bin/opener-bin.js" - } - }, - "node_modules/npm/node_modules/p-map": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/pacote": { - "version": "13.6.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^3.0.0", - "@npmcli/installed-package-contents": "^1.0.7", - "@npmcli/promise-spawn": "^3.0.0", - "@npmcli/run-script": "^4.1.0", - "cacache": "^16.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "infer-owner": "^1.0.4", - "minipass": "^3.1.6", - "mkdirp": "^1.0.4", - "npm-package-arg": "^9.0.0", - "npm-packlist": "^5.1.0", - "npm-pick-manifest": "^7.0.0", - "npm-registry-fetch": "^13.0.1", - "proc-log": "^2.0.0", - "promise-retry": "^2.0.1", - "read-package-json": "^5.0.0", - "read-package-json-fast": "^2.0.3", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "lib/bin.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/parse-conflict-json": { - "version": "2.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^2.3.1", - "just-diff": "^5.0.1", - "just-diff-apply": "^5.2.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/path-is-absolute": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/proc-log": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/promise-all-reject-late": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/promise-call-limit": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/promise-inflight": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/promise-retry": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/promzard": { - "version": "0.3.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "read": "1" - } - }, - "node_modules/npm/node_modules/qrcode-terminal": { - "version": "0.12.0", - "dev": true, - "inBundle": true, - "bin": { - "qrcode-terminal": "bin/qrcode-terminal.js" - } - }, - "node_modules/npm/node_modules/read": { - "version": "1.0.7", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "mute-stream": "~0.0.4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/npm/node_modules/read-cmd-shim": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/read-package-json": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^8.0.1", - "json-parse-even-better-errors": "^2.3.1", - "normalize-package-data": "^4.0.0", - "npm-normalize-package-bin": "^1.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/read-package-json-fast": { - "version": "2.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^2.3.0", - "npm-normalize-package-bin": "^1.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/readable-stream": { - "version": "3.6.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/npm/node_modules/readdir-scoped-modules": { - "version": "1.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" - } - }, - "node_modules/npm/node_modules/retry": { - "version": "0.12.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/npm/node_modules/rimraf": { - "version": "3.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.11", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/npm/node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npm/node_modules/safe-buffer": { - "version": "5.2.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/safer-buffer": { - "version": "2.1.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true - }, - "node_modules/npm/node_modules/semver": { - "version": "7.3.7", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/set-blocking": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/signal-exit": { - "version": "3.0.7", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/smart-buffer": { - "version": "4.2.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/npm/node_modules/socks": { - "version": "2.6.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ip": "^1.1.5", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/npm/node_modules/socks-proxy-agent": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/npm/node_modules/spdx-correct": { - "version": "3.1.1", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-exceptions": { - "version": "2.3.0", - "dev": true, - "inBundle": true, - "license": "CC-BY-3.0" - }, - "node_modules/npm/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.11", - "dev": true, - "inBundle": true, - "license": "CC0-1.0" - }, - "node_modules/npm/node_modules/ssri": { - "version": "9.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/string_decoder": { - "version": "1.3.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/npm/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/tar": { - "version": "6.1.11", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/npm/node_modules/text-table": { - "version": "0.2.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/tiny-relative-date": { - "version": "1.3.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/treeverse": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/unique-filename": { - "version": "1.1.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/npm/node_modules/unique-slug": { - "version": "2.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, - "node_modules/npm/node_modules/util-deprecate": { - "version": "1.0.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/validate-npm-package-license": { - "version": "3.0.4", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "builtins": "^5.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/walk-up-path": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/wcwidth": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/npm/node_modules/which": { - "version": "2.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/wide-align": { - "version": "1.1.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/npm/node_modules/wrappy": { - "version": "1.0.2", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/write-file-atomic": { - "version": "4.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" - } - }, - "node_modules/npm/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/nwsapi": { - "version": "2.2.0", - "license": "MIT" - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-is": { - "version": "1.1.5", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/p-each-series": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-filter": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-map": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-filter/node_modules/p-map": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/p-is-promise": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-reduce": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/p-retry": { - "version": "4.6.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/retry": "^0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pako": { - "version": "0.2.9", - "license": "MIT" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parent-module/node_modules/callsites": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-path": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", - "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", - "dev": true, - "dependencies": { - "protocols": "^2.0.0" - } - }, - "node_modules/parse-url": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", - "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", - "dev": true, - "dependencies": { - "parse-path": "^7.0.0" - } - }, - "node_modules/parse5": { - "version": "6.0.1", - "license": "MIT" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pdfkit": { - "version": "0.12.3", - "license": "MIT", - "dependencies": { - "crypto-js": "^4.0.0", - "fontkit": "^1.8.1", - "linebreak": "^1.0.2", - "png-js": "^1.0.0" - } - }, - "node_modules/pdfmake": { - "version": "0.2.2", - "license": "MIT", - "dependencies": { - "@foliojs-fork/linebreak": "^1.1.1", - "@foliojs-fork/pdfkit": "^0.12.3", - "iconv-lite": "^0.6.3", - "svg-to-pdfkit": "^0.1.8", - "xmldoc": "^1.1.2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/pdfmake/node_modules/iconv-lite": { - "version": "0.6.3", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pegjs": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", - "integrity": "sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==", - "dev": true, - "bin": { - "pegjs": "bin/pegjs" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/performance-now": { - "version": "2.1.0", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.0.0", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", - "dev": true, - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pkg-conf": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^2.0.0", - "load-json-file": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-conf/node_modules/find-up": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-conf/node_modules/locate-path": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-conf/node_modules/p-limit": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-conf/node_modules/p-locate": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-conf/node_modules/p-try": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-conf/node_modules/path-exists": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/png-js": { - "version": "1.0.0" - }, - "node_modules/polylabel": { - "version": "1.1.0", - "license": "ISC", - "dependencies": { - "tinyqueue": "^2.0.3" - } - }, - "node_modules/postcss": { - "version": "8.4.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", - "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - } - ], - "dependencies": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-attribute-case-insensitive": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", - "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-calc": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", - "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.9", - "postcss-value-parser": "^4.2.0" - }, - "peerDependencies": { - "postcss": "^8.2.2" - } - }, - "node_modules/postcss-clamp": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", - "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=7.6.0" - }, - "peerDependencies": { - "postcss": "^8.4.6" - } - }, - "node_modules/postcss-color-functional-notation": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", - "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-color-hex-alpha": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", - "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-color-rebeccapurple": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", - "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-colormin": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.0.tgz", - "integrity": "sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==", - "dev": true, - "dependencies": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0", - "colord": "^2.9.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-convert-values": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.2.tgz", - "integrity": "sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g==", - "dev": true, - "dependencies": { - "browserslist": "^4.20.3", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-custom-media": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", - "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.3" - } - }, - "node_modules/postcss-custom-properties": { - "version": "12.1.9", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.9.tgz", - "integrity": "sha512-/E7PRvK8DAVljBbeWrcEQJPG72jaImxF3vvCNFwv9cC8CzigVoNIpeyfnJzphnN3Fd8/auBf5wvkw6W9MfmTyg==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-custom-selectors": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", - "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.3" - } - }, - "node_modules/postcss-dir-pseudo-class": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", - "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-discard-comments": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", - "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-duplicates": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-empty": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-overridden": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-double-position-gradients": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", - "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", - "dev": true, - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-env-function": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", - "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-visible": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", - "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.9" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-within": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", - "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.9" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-font-variant": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", - "dev": true, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-gap-properties": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", - "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", - "dev": true, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-image-set-function": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", - "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-import": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.0.0.tgz", - "integrity": "sha512-Y20shPQ07RitgBGv2zvkEAu9bqvrD77C9axhj/aA1BQj4czape2MdClCExvB27EwYEJdGgKZBpKanb0t1rK2Kg==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-initial": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", - "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", - "dev": true, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz", - "integrity": "sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==", - "dev": true, - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.3.3" - } - }, - "node_modules/postcss-lab-function": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", - "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", - "dev": true, - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", - "dev": true, - "dependencies": { - "lilconfig": "^2.0.5", - "yaml": "^1.10.2" - }, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-logical": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", - "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", - "dev": true, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-media-minmax": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", - "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-media-query-parser": { - "version": "0.2.3", - "dev": true, - "license": "MIT" - }, - "node_modules/postcss-merge-longhand": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.6.tgz", - "integrity": "sha512-6C/UGF/3T5OE2CEbOuX7iNO63dnvqhGZeUnKkDeifebY0XqkkvrctYSZurpNE902LDf2yKwwPFgotnfSoPhQiw==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^5.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-merge-rules": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.2.tgz", - "integrity": "sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ==", - "dev": true, - "dependencies": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^3.1.0", - "postcss-selector-parser": "^6.0.5" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-font-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", - "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-gradients": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", - "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", - "dev": true, - "dependencies": { - "colord": "^2.9.1", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-params": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz", - "integrity": "sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg==", - "dev": true, - "dependencies": { - "browserslist": "^4.16.6", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-selectors": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", - "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.5" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-nested": { - "version": "5.0.6", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.6" - }, - "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-nesting": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", - "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", - "dev": true, - "dependencies": { - "@csstools/selector-specificity": "^2.0.0", - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-normalize-charset": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-display-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", - "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-positions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", - "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-repeat-style": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", - "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-string": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", - "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-timing-functions": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", - "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-unicode": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz", - "integrity": "sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ==", - "dev": true, - "dependencies": { - "browserslist": "^4.16.6", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", - "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", - "dev": true, - "dependencies": { - "normalize-url": "^6.0.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-whitespace": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", - "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-opacity-percentage": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.2.tgz", - "integrity": "sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w==", - "dev": true, - "funding": [ - { - "type": "kofi", - "url": "https://ko-fi.com/mrcgrtz" - }, - { - "type": "liberapay", - "url": "https://liberapay.com/mrcgrtz" - } - ], - "engines": { - "node": "^12 || ^14 || >=16" - } - }, - "node_modules/postcss-ordered-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", - "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", - "dev": true, - "dependencies": { - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-overflow-shorthand": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", - "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-page-break": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", - "dev": true, - "peerDependencies": { - "postcss": "^8" - } - }, - "node_modules/postcss-place": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", - "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-preset-env": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.2.tgz", - "integrity": "sha512-rSMUEaOCnovKnwc5LvBDHUDzpGP+nrUeWZGWt9M72fBvckCi45JmnJigUr4QG4zZeOHmOCNCZnd2LKDvP++ZuQ==", - "dev": true, - "dependencies": { - "@csstools/postcss-cascade-layers": "^1.1.0", - "@csstools/postcss-color-function": "^1.1.1", - "@csstools/postcss-font-format-keywords": "^1.0.1", - "@csstools/postcss-hwb-function": "^1.0.2", - "@csstools/postcss-ic-unit": "^1.0.1", - "@csstools/postcss-is-pseudo-class": "^2.0.7", - "@csstools/postcss-nested-calc": "^1.0.0", - "@csstools/postcss-normalize-display-values": "^1.0.1", - "@csstools/postcss-oklab-function": "^1.1.1", - "@csstools/postcss-progressive-custom-properties": "^1.3.0", - "@csstools/postcss-stepped-value-functions": "^1.0.1", - "@csstools/postcss-text-decoration-shorthand": "^1.0.0", - "@csstools/postcss-trigonometric-functions": "^1.0.2", - "@csstools/postcss-unset-value": "^1.0.2", - "autoprefixer": "^10.4.11", - "browserslist": "^4.21.3", - "css-blank-pseudo": "^3.0.3", - "css-has-pseudo": "^3.0.4", - "css-prefers-color-scheme": "^6.0.3", - "cssdb": "^7.0.1", - "postcss-attribute-case-insensitive": "^5.0.2", - "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^4.2.4", - "postcss-color-hex-alpha": "^8.0.4", - "postcss-color-rebeccapurple": "^7.1.1", - "postcss-custom-media": "^8.0.2", - "postcss-custom-properties": "^12.1.9", - "postcss-custom-selectors": "^6.0.3", - "postcss-dir-pseudo-class": "^6.0.5", - "postcss-double-position-gradients": "^3.1.2", - "postcss-env-function": "^4.0.6", - "postcss-focus-visible": "^6.0.4", - "postcss-focus-within": "^5.0.4", - "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^3.0.5", - "postcss-image-set-function": "^4.0.7", - "postcss-initial": "^4.0.1", - "postcss-lab-function": "^4.2.1", - "postcss-logical": "^5.0.4", - "postcss-media-minmax": "^5.0.0", - "postcss-nesting": "^10.2.0", - "postcss-opacity-percentage": "^1.1.2", - "postcss-overflow-shorthand": "^3.0.4", - "postcss-page-break": "^3.0.4", - "postcss-place": "^7.0.5", - "postcss-pseudo-class-any-link": "^7.1.6", - "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^6.0.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-pseudo-class-any-link": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", - "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-reduce-initial": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz", - "integrity": "sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw==", - "dev": true, - "dependencies": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-reduce-transforms": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", - "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-replace-overflow-wrap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", - "dev": true, - "peerDependencies": { - "postcss": "^8.0.3" - } - }, - "node_modules/postcss-reporter": { - "version": "7.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "picocolors": "^1.0.0", - "thenby": "^1.3.4" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-resolve-nested-selector": { - "version": "0.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/postcss-safe-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", - "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", - "dev": true, - "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.3.3" - } - }, - "node_modules/postcss-selector-not": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", - "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-svgo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", - "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0", - "svgo": "^2.7.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-unique-selectors": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", - "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.5" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/prettier-plugin-organize-imports": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.1.1.tgz", - "integrity": "sha512-6bHIQzybqA644h0WGUW3gpWEVbMBvzui5wCMRBi7qA++d5ov2xjjfDk8pxJJ/ardfZrGAwizKMq/fQMFdJ+0Zw==", - "dev": true, - "peerDependencies": { - "@volar/vue-typescript": ">=0.40.2", - "prettier": ">=2.0", - "typescript": ">=2.9" - }, - "peerDependenciesMeta": { - "@volar/vue-typescript": { - "optional": true - } - } - }, - "node_modules/pretty-bytes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.0.0.tgz", - "integrity": "sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg==", - "dev": true, - "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/printj": { - "version": "1.1.2", - "license": "Apache-2.0", - "bin": { - "printj": "bin/printj.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "license": "MIT" - }, - "node_modules/protocols": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", - "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==", - "dev": true - }, - "node_modules/psl": { - "version": "1.8.0", - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/q": { - "version": "1.5.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/quick-lru": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/quote-stream": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "buffer-equal": "0.0.1", - "minimist": "^1.1.3", - "through2": "^2.0.0" - }, - "bin": { - "quote-stream": "bin/cmd.js" - } - }, - "node_modules/quote-stream/node_modules/through2": { - "version": "2.0.5", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/raf": { - "version": "3.4.1", - "license": "MIT", - "dependencies": { - "performance-now": "^2.1.0" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-cache": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "dev": true, - "license": "ISC" - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/read-pkg/node_modules/parse-json": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/redent": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/redeyed": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", - "integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=", - "dev": true, - "dependencies": { - "esprima": "~4.0.0" - } - }, - "node_modules/redux": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz", - "integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", - "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.7", - "license": "MIT" - }, - "node_modules/regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/regexpu-core": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.1.0.tgz", - "integrity": "sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.0.1", - "regjsgen": "^0.6.0", - "regjsparser": "^0.8.2", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/regjsgen": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", - "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", - "dev": true - }, - "node_modules/regjsparser": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", - "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", - "dev": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/regression": { - "version": "2.0.1", - "license": "MIT" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "dev": true, - "license": "MIT" - }, - "node_modules/resolve-dir": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-global": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "global-dirs": "^0.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/responselike": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "lowercase-keys": "^2.0.0" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/restructure": { - "version": "0.5.4", - "license": "MIT", - "dependencies": { - "browserify-optional": "^1.0.0" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, - "node_modules/rgbcolor": { - "version": "1.0.1", - "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", - "engines": { - "node": ">= 0.8.15" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "2.75.7", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.7.tgz", - "integrity": "sha512-VSE1iy0eaAYNCxEXaleThdFXqZJ42qDBatAwrfnPlENEZ8erQ+0LYX4JXOLPceWfZpV1VtZwZ3dFCuOZiSyFtQ==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/rollup-plugin-terser": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", - "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "jest-worker": "^26.2.1", - "serialize-javascript": "^4.0.0", - "terser": "^5.0.0" - }, - "peerDependencies": { - "rollup": "^2.0.0" - } - }, - "node_modules/run-async": { - "version": "2.4.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/run-parallel": { - "version": "1.1.9", - "dev": true, - "license": "MIT" - }, - "node_modules/rxjs": { - "version": "6.6.2", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "license": "MIT" - }, - "node_modules/sax": { - "version": "1.2.4", - "license": "ISC" - }, - "node_modules/saxes": { - "version": "5.0.1", - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/scope-analyzer": { - "version": "2.1.1", - "license": "Apache-2.0", - "dependencies": { - "array-from": "^2.1.1", - "dash-ast": "^1.0.0", - "es6-map": "^0.1.5", - "es6-set": "^0.1.5", - "es6-symbol": "^3.1.1", - "estree-is-function": "^1.0.0", - "get-assigned-identifiers": "^1.1.0" - } - }, - "node_modules/semantic-release": { - "version": "19.0.5", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-19.0.5.tgz", - "integrity": "sha512-NMPKdfpXTnPn49FDogMBi36SiBfXkSOJqCkk0E4iWOY1tusvvgBwqUmxTX1kmlT6kIYed9YwNKD1sfPpqa5yaA==", - "dev": true, - "dependencies": { - "@semantic-release/commit-analyzer": "^9.0.2", - "@semantic-release/error": "^3.0.0", - "@semantic-release/github": "^8.0.0", - "@semantic-release/npm": "^9.0.0", - "@semantic-release/release-notes-generator": "^10.0.0", - "aggregate-error": "^3.0.0", - "cosmiconfig": "^7.0.0", - "debug": "^4.0.0", - "env-ci": "^5.0.0", - "execa": "^5.0.0", - "figures": "^3.0.0", - "find-versions": "^4.0.0", - "get-stream": "^6.0.0", - "git-log-parser": "^1.2.0", - "hook-std": "^2.0.0", - "hosted-git-info": "^4.0.0", - "lodash": "^4.17.21", - "marked": "^4.0.10", - "marked-terminal": "^5.0.0", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", - "p-reduce": "^2.0.0", - "read-pkg-up": "^7.0.0", - "resolve-from": "^5.0.0", - "semver": "^7.3.2", - "semver-diff": "^3.1.1", - "signale": "^1.2.1", - "yargs": "^16.2.0" - }, - "bin": { - "semantic-release": "bin/semantic-release.js" - }, - "engines": { - "node": ">=16 || ^14.17" - } - }, - "node_modules/semantic-release/node_modules/figures": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/semantic-release/node_modules/get-stream": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/semantic-release/node_modules/semver": { - "version": "7.3.5", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "6.3.0", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/semver-diff": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/semver-regex": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz", - "integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "node_modules/shallow-copy": { - "version": "0.0.1", - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/signale": { - "version": "1.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^2.3.2", - "figures": "^2.0.0", - "pkg-conf": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/signale/node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.5.7", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "license": "MIT" - }, - "node_modules/spawn-error-forwarder": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.7", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/split": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/split2": { - "version": "3.2.2", - "dev": true, - "license": "ISC", - "dependencies": { - "readable-stream": "^3.0.0" - } - }, - "node_modules/split2/node_modules/readable-stream": { - "version": "3.6.0", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/ssf": { - "version": "0.11.2", - "license": "Apache-2.0", - "dependencies": { - "frac": "~1.1.2" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/stable": { - "version": "0.1.8", - "dev": true, - "license": "MIT" - }, - "node_modules/stackblur-canvas": { - "version": "2.5.0", - "license": "MIT", - "engines": { - "node": ">=0.1.14" - } - }, - "node_modules/static-eval": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "escodegen": "^1.11.1" - } - }, - "node_modules/static-eval/node_modules/escodegen": { - "version": "1.14.3", - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/static-eval/node_modules/levn": { - "version": "0.3.0", - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/optionator": { - "version": "0.8.3", - "license": "MIT", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/prelude-ls": { - "version": "1.1.2", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/source-map": { - "version": "0.6.1", - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-eval/node_modules/type-check": { - "version": "0.3.2", - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-module": { - "version": "3.0.4", - "license": "MIT", - "dependencies": { - "acorn-node": "^1.3.0", - "concat-stream": "~1.6.0", - "convert-source-map": "^1.5.1", - "duplexer2": "~0.1.4", - "escodegen": "^1.11.1", - "has": "^1.0.1", - "magic-string": "0.25.1", - "merge-source-map": "1.0.4", - "object-inspect": "^1.6.0", - "readable-stream": "~2.3.3", - "scope-analyzer": "^2.0.1", - "shallow-copy": "~0.0.1", - "static-eval": "^2.0.5", - "through2": "~2.0.3" - } - }, - "node_modules/static-module/node_modules/escodegen": { - "version": "1.14.3", - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/static-module/node_modules/levn": { - "version": "0.3.0", - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-module/node_modules/optionator": { - "version": "0.8.3", - "license": "MIT", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-module/node_modules/prelude-ls": { - "version": "1.1.2", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-module/node_modules/source-map": { - "version": "0.6.1", - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-module/node_modules/through2": { - "version": "2.0.5", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/static-module/node_modules/type-check": { - "version": "0.3.2", - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/stencil-wormhole": { - "version": "3.4.1", - "license": "MIT" - }, - "node_modules/stream-combiner2": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string-argv": { - "version": "0.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", - "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.1", - "side-channel": "^1.0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dev": true, - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/stringify-object/node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", - "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-indent": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/style-mod": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz", - "integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==" - }, - "node_modules/style-search": { - "version": "0.1.0", - "dev": true, - "license": "ISC" - }, - "node_modules/stylehacks": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", - "integrity": "sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==", - "dev": true, - "dependencies": { - "browserslist": "^4.16.6", - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/stylelint": { - "version": "14.13.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.13.0.tgz", - "integrity": "sha512-NJSAdloiAB/jgVJKxMR90mWlctvmeBFGFVUvyKngi9+j/qPSJ5ZB+u8jOmGbLTnS7OHrII9NFGehPRyar8U5vg==", - "dev": true, - "dependencies": { - "@csstools/selector-specificity": "^2.0.2", - "balanced-match": "^2.0.0", - "colord": "^2.9.3", - "cosmiconfig": "^7.0.1", - "css-functions-list": "^3.1.0", - "debug": "^4.3.4", - "fast-glob": "^3.2.12", - "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^6.0.1", - "global-modules": "^2.0.0", - "globby": "^11.1.0", - "globjoin": "^0.1.4", - "html-tags": "^3.2.0", - "ignore": "^5.2.0", - "import-lazy": "^4.0.0", - "imurmurhash": "^0.1.4", - "is-plain-object": "^5.0.0", - "known-css-properties": "^0.25.0", - "mathml-tag-names": "^2.1.3", - "meow": "^9.0.0", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.16", - "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.10", - "postcss-value-parser": "^4.2.0", - "resolve-from": "^5.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "style-search": "^0.1.0", - "supports-hyperlinks": "^2.3.0", - "svg-tags": "^1.0.0", - "table": "^6.8.0", - "v8-compile-cache": "^2.3.0", - "write-file-atomic": "^4.0.2" - }, - "bin": { - "stylelint": "bin/stylelint.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" - } - }, - "node_modules/stylelint-config-recommended": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-9.0.0.tgz", - "integrity": "sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==", - "dev": true, - "peerDependencies": { - "stylelint": "^14.10.0" - } - }, - "node_modules/stylelint-config-standard": { - "version": "28.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-28.0.0.tgz", - "integrity": "sha512-q/StuowDdDmFCravzGHAwgS9pjX0bdOQUEBBDIkIWsQuYGgYz/xsO8CM6eepmIQ1fc5bKdDVimlJZ6MoOUcJ5Q==", - "dev": true, - "dependencies": { - "stylelint-config-recommended": "^9.0.0" - }, - "peerDependencies": { - "stylelint": "^14.11.0" - } - }, - "node_modules/stylelint/node_modules/balanced-match": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/stylelint/node_modules/global-modules": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/stylelint/node_modules/global-prefix": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/stylelint/node_modules/meow": { - "version": "9.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svg-tags": { - "version": "1.0.0", - "dev": true - }, - "node_modules/svg-to-pdfkit": { - "version": "0.1.8", - "license": "MIT", - "dependencies": { - "pdfkit": ">=0.8.1" - } - }, - "node_modules/svgo": { - "version": "2.8.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/svgo/node_modules/commander": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/svgo/node_modules/css-select": { - "version": "4.1.3", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^5.0.0", - "domhandler": "^4.2.0", - "domutils": "^2.6.0", - "nth-check": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/svgo/node_modules/css-what": { - "version": "5.0.1", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/svgo/node_modules/dom-serializer": { - "version": "1.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/svgo/node_modules/domelementtype": { - "version": "2.2.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/svgo/node_modules/domhandler": { - "version": "4.2.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/svgo/node_modules/domutils": { - "version": "2.7.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/svgo/node_modules/nth-check": { - "version": "2.0.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "license": "MIT" - }, - "node_modules/table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", - "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/table/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/table/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/table/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/tailwindcss": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.1.8.tgz", - "integrity": "sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==", - "dev": true, - "dependencies": { - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "color-name": "^1.1.4", - "detective": "^5.2.1", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.2.11", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "lilconfig": "^2.0.6", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.14", - "postcss-import": "^14.1.0", - "postcss-js": "^4.0.0", - "postcss-load-config": "^3.1.4", - "postcss-nested": "5.0.6", - "postcss-selector-parser": "^6.0.10", - "postcss-value-parser": "^4.2.0", - "quick-lru": "^5.1.1", - "resolve": "^1.22.1" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=12.13.0" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } - }, - "node_modules/tailwindcss/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/tailwindcss/node_modules/glob-parent": { - "version": "6.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/tailwindcss/node_modules/lilconfig": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", - "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/tailwindcss/node_modules/postcss-import": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", - "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/tailwindcss/node_modules/quick-lru": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/temp-dir": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/tempy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-1.0.1.tgz", - "integrity": "sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==", - "dev": true, - "dependencies": { - "del": "^6.0.0", - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/text-extensions": { - "version": "1.9.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/thenby": { - "version": "1.3.4", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/through": { - "version": "2.3.8", - "license": "MIT" - }, - "node_modules/through2": { - "version": "4.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "3" - } - }, - "node_modules/through2/node_modules/readable-stream": { - "version": "3.6.0", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/tiny-inflate": { - "version": "1.0.3", - "license": "MIT" - }, - "node_modules/tinyqueue": { - "version": "2.0.3", - "license": "ISC" - }, - "node_modules/tmp": { - "version": "0.0.33", - "dev": true, - "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tough-cookie": { - "version": "4.0.0", - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tr46": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/traverse": { - "version": "0.6.6", - "dev": true, - "license": "MIT" - }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-node": { - "version": "10.8.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.1.tgz", - "integrity": "sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ts-node/node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ts-node/node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/tslib": { - "version": "1.13.0", - "dev": true, - "license": "0BSD" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/type": { - "version": "1.2.0", - "license": "ISC" - }, - "node_modules/type-check": { - "version": "0.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.18.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "license": "MIT" - }, - "node_modules/typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/uglify-js": { - "version": "3.14.5", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-properties": { - "version": "1.3.1", - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.0", - "unicode-trie": "^2.0.0" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-trie": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "pako": "^0.2.5", - "tiny-inflate": "^1.0.0" - } - }, - "node_modules/unique-string": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/universal-user-agent": { - "version": "6.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/universalify": { - "version": "0.1.2", - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", - "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-join": { - "version": "4.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "license": "MIT" - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "dev": true, - "license": "MIT" - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/vite": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-2.8.6.tgz", - "integrity": "sha512-e4H0QpludOVKkmOsRyqQ7LTcMUDF3mcgyNU4lmi0B5JUbe0ZxeBBl8VoZ8Y6Rfn9eFKYtdXNPcYK97ZwH+K2ug==", - "dev": true, - "dependencies": { - "esbuild": "^0.14.14", - "postcss": "^8.4.6", - "resolve": "^1.22.0", - "rollup": "^2.59.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": ">=12.2.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "less": "*", - "sass": "*", - "stylus": "*" - }, - "peerDependenciesMeta": { - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - } - } - }, - "node_modules/vite-plugin-pwa": { - "version": "0.12.8", - "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.12.8.tgz", - "integrity": "sha512-pSiFHmnJGMQJJL8aJzQ8SaraZBSBPMGvGUkCNzheIq9UQCEk/eP3UmANNmS9eupuhIpTK8AdxTOHcaMcAqAbCA==", - "dev": true, - "dependencies": { - "debug": "^4.3.4", - "fast-glob": "^3.2.11", - "pretty-bytes": "^6.0.0", - "rollup": "^2.75.7", - "workbox-build": "^6.5.3", - "workbox-window": "^6.5.3" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": "^2.0.0 || ^3.0.0-0", - "workbox-build": "^6.4.0", - "workbox-window": "^6.4.0" - } - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-keyname": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.4.tgz", - "integrity": "sha512-tOhfEwEzFLJzf6d1ZPkYfGj+FWhIpBux9ppoP3rlclw3Z0BZv3N7b7030Z1kYth+6rDuAsXUFr+d0VE6Ed1ikw==" - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/wavesurfer.js": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-6.3.0.tgz", - "integrity": "sha512-x7efObHMHY3nwqWzIC0yeZTo0u/aC9T5Av6KhSdhTlsgtKdTG7JAE3mNqnYBXUjq0yGYfTB9F85/pks9830Vjw==" - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "license": "BSD-2-Clause", - "engines": { - "node": ">=10.4" - } - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "license": "MIT", - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "license": "MIT" - }, - "node_modules/whatwg-url": { - "version": "8.7.0", - "license": "MIT", - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/which": { - "version": "1.3.1", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", - "dev": true - }, - "node_modules/wmf": { - "version": "1.0.2", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/word": { - "version": "0.4.0", - "license": "Apache-2.0", - "dependencies": { - "cfb": "^1.2.0", - "jsdom": "^16.2.2" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/workbox-background-sync": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz", - "integrity": "sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==", - "dev": true, - "dependencies": { - "idb": "^7.0.1", - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-broadcast-update": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz", - "integrity": "sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==", - "dev": true, - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-build": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.5.4.tgz", - "integrity": "sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==", - "dev": true, - "dependencies": { - "@apideck/better-ajv-errors": "^0.3.1", - "@babel/core": "^7.11.1", - "@babel/preset-env": "^7.11.0", - "@babel/runtime": "^7.11.2", - "@rollup/plugin-babel": "^5.2.0", - "@rollup/plugin-node-resolve": "^11.2.1", - "@rollup/plugin-replace": "^2.4.1", - "@surma/rollup-plugin-off-main-thread": "^2.2.3", - "ajv": "^8.6.0", - "common-tags": "^1.8.0", - "fast-json-stable-stringify": "^2.1.0", - "fs-extra": "^9.0.1", - "glob": "^7.1.6", - "lodash": "^4.17.20", - "pretty-bytes": "^5.3.0", - "rollup": "^2.43.1", - "rollup-plugin-terser": "^7.0.0", - "source-map": "^0.8.0-beta.0", - "stringify-object": "^3.3.0", - "strip-comments": "^2.0.1", - "tempy": "^0.6.0", - "upath": "^1.2.0", - "workbox-background-sync": "6.5.4", - "workbox-broadcast-update": "6.5.4", - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-google-analytics": "6.5.4", - "workbox-navigation-preload": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-range-requests": "6.5.4", - "workbox-recipes": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4", - "workbox-streams": "6.5.4", - "workbox-sw": "6.5.4", - "workbox-window": "6.5.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/workbox-build/node_modules/@apideck/better-ajv-errors": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", - "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", - "dev": true, - "dependencies": { - "json-schema": "^0.4.0", - "jsonpointer": "^5.0.0", - "leven": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "ajv": ">=8" - } - }, - "node_modules/workbox-build/node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/workbox-build/node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "dev": true, - "dependencies": { - "whatwg-url": "^7.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/workbox-build/node_modules/tempy": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", - "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", - "dev": true, - "dependencies": { - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/workbox-build/node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/workbox-build/node_modules/type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/workbox-build/node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "node_modules/workbox-build/node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "node_modules/workbox-cacheable-response": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz", - "integrity": "sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==", - "dev": true, - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-core": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.5.4.tgz", - "integrity": "sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==", - "dev": true - }, - "node_modules/workbox-expiration": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.5.4.tgz", - "integrity": "sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==", - "dev": true, - "dependencies": { - "idb": "^7.0.1", - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-google-analytics": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz", - "integrity": "sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==", - "dev": true, - "dependencies": { - "workbox-background-sync": "6.5.4", - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "node_modules/workbox-navigation-preload": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz", - "integrity": "sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==", - "dev": true, - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-precaching": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.5.4.tgz", - "integrity": "sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==", - "dev": true, - "dependencies": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "node_modules/workbox-range-requests": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz", - "integrity": "sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==", - "dev": true, - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-recipes": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.5.4.tgz", - "integrity": "sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==", - "dev": true, - "dependencies": { - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "node_modules/workbox-routing": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.5.4.tgz", - "integrity": "sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==", - "dev": true, - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-strategies": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.5.4.tgz", - "integrity": "sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==", - "dev": true, - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-streams": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.5.4.tgz", - "integrity": "sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==", - "dev": true, - "dependencies": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4" - } - }, - "node_modules/workbox-sw": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.5.4.tgz", - "integrity": "sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==", - "dev": true - }, - "node_modules/workbox-window": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.5.4.tgz", - "integrity": "sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==", - "dev": true, - "dependencies": { - "@types/trusted-types": "^2.0.2", - "workbox-core": "6.5.4" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/wrappy": { - "version": "1.0.2", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/ws": { - "version": "7.5.5", - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xlsx": { - "version": "0.17.2", - "license": "Apache-2.0", - "dependencies": { - "adler-32": "~1.2.0", - "cfb": "^1.1.4", - "codepage": "~1.15.0", - "commander": "~2.17.1", - "crc-32": "~1.2.0", - "exit-on-epipe": "~1.0.1", - "fflate": "^0.3.8", - "ssf": "~0.11.2", - "wmf": "~1.0.1", - "word": "~0.4.0" - }, - "bin": { - "xlsx": "bin/xlsx.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/xlsx/node_modules/commander": { - "version": "2.17.1", - "license": "MIT" - }, - "node_modules/xml-formatter": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/xml-formatter/-/xml-formatter-2.6.1.tgz", - "integrity": "sha512-dOiGwoqm8y22QdTNI7A+N03tyVfBlQ0/oehAzxIZtwnFAHGeSlrfjF73YQvzSsa/Kt6+YZasKsrdu6OIpuBggw==", - "dependencies": { - "xml-parser-xo": "^3.2.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "license": "Apache-2.0" - }, - "node_modules/xml-parser-xo": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/xml-parser-xo/-/xml-parser-xo-3.2.0.tgz", - "integrity": "sha512-8LRU6cq+d7mVsoDaMhnkkt3CTtAs4153p49fRo+HIB3I1FD1o5CeXRjRH29sQevIfVJIcPjKSsPU/+Ujhq09Rg==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "license": "MIT" - }, - "node_modules/xmldoc": { - "version": "1.1.2", - "license": "MIT", - "dependencies": { - "sax": "^1.2.1" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.5", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/yaml": { - "version": "1.10.2", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.7", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@amcharts/amcharts4": { - "version": "4.10.29", - "resolved": "https://registry.npmjs.org/@amcharts/amcharts4/-/amcharts4-4.10.29.tgz", - "integrity": "sha512-uDCvm4V0Xs2jtI0Aa7XFH0jqoyEGx9I2ukFEwaaQzYq11vwowhJsgE3sSv2jsfKETYUvPXb32NbzrXZulE2ESg==", - "requires": { - "@babel/runtime": "^7.6.3", - "core-js": "^3.0.0", - "d3-force": "^3.0.0", - "d3-geo": "^3.0.1", - "d3-geo-projection": "^4.0.0", - "d3-selection": "^3.0.0", - "d3-transition": "^3.0.1", - "pdfmake": "^0.2.2", - "polylabel": "^1.0.2", - "raf": "^3.4.1", - "regression": "^2.0.1", - "rgbcolor": "^1.0.1", - "stackblur-canvas": "^2.0.0", - "tslib": "^2.0.1", - "xlsx": "^0.17.0" - }, - "dependencies": { - "tslib": { - "version": "2.1.0" - } - } - }, - "@amcharts/amcharts4-geodata": { - "version": "4.1.23", - "resolved": "https://registry.npmjs.org/@amcharts/amcharts4-geodata/-/amcharts4-geodata-4.1.23.tgz", - "integrity": "sha512-/hFqTuc2SUB0tAgygc8yj1xvTUdddltI7/15fAT9L5XHGERgF8qAa+AisQQ0UMITvrWIr5awJupWE3vDTlWsjQ==" - }, - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/compat-data": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.6.tgz", - "integrity": "sha512-tzulrgDT0QD6U7BJ4TKVk2SDDg7wlP39P9yAx1RfLy7vP/7rsDRlWVfbWxElslu56+r7QOhB2NSDsabYYruoZQ==", - "dev": true - }, - "@babel/core": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.6.tgz", - "integrity": "sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.6", - "@babel/helper-compilation-targets": "^7.18.6", - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helpers": "^7.18.6", - "@babel/parser": "^7.18.6", - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.6", - "@babel/types": "^7.18.6", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - } - }, - "@babel/generator": { - "version": "7.18.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.7.tgz", - "integrity": "sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A==", - "dev": true, - "requires": { - "@babel/types": "^7.18.7", - "@jridgewell/gen-mapping": "^0.3.2", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.6.tgz", - "integrity": "sha512-KT10c1oWEpmrIRYnthbzHgoOf6B+Xd6a5yhdbNtdhtG7aO1or5HViuf1TQR36xY/QprXA5nvxO6nAjhJ4y38jw==", - "dev": true, - "requires": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.6.tgz", - "integrity": "sha512-vFjbfhNCzqdeAtZflUFrG5YIFqGTqsctrtkZ1D/NB0mDW9TwW3GmmUepYY4G9wCET5rY5ugz4OGTcLd614IzQg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.20.2", - "semver": "^6.3.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.6.tgz", - "integrity": "sha512-YfDzdnoxHGV8CzqHGyCbFvXg5QESPFkXlHtvdCkesLjjVMT2Adxe4FGUR5ChIb3DxSaXO12iIOCWoXdsUVwnqw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.6", - "@babel/helper-function-name": "^7.18.6", - "@babel/helper-member-expression-to-functions": "^7.18.6", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz", - "integrity": "sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.1.0" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", - "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", - "dev": true, - "requires": { - "@babel/helper-compilation-targets": "^7.13.0", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/traverse": "^7.13.0", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz", - "integrity": "sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q==", - "dev": true - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-function-name": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.6.tgz", - "integrity": "sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw==", - "dev": true, - "requires": { - "@babel/template": "^7.18.6", - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.6.tgz", - "integrity": "sha512-CeHxqwwipekotzPDUuJOfIMtcIHBuc7WAzLmTYWctVigqS5RktNMQ5bEwQSuGewzYnCtTWa3BARXeiLxDTv+Ng==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-transforms": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.6.tgz", - "integrity": "sha512-L//phhB4al5uucwzlimruukHB3jRd5JGClwRMD/ROrVjXfLqovYnvQrK/JK36WYyVwGGO7OD3kMyVTjx+WVPhw==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.6", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.6", - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz", - "integrity": "sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg==", - "dev": true - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.6.tgz", - "integrity": "sha512-z5wbmV55TveUPZlCLZvxWHtrjuJd+8inFhk7DG0WW87/oJuGDcjDiu7HIvGcpf5464L6xKCg3vNkmlVVz9hwyQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.6", - "@babel/helper-wrap-function": "^7.18.6", - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-replace-supers": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.6.tgz", - "integrity": "sha512-fTf7zoXnUGl9gF25fXCWE26t7Tvtyn6H4hkLSYhATwJvw2uYxd3aoXplMSe0g9XbwK7bmxNes7+FGO0rB/xC0g==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.6", - "@babel/helper-member-expression-to-functions": "^7.18.6", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/traverse": "^7.18.6", - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.6.tgz", - "integrity": "sha512-4KoLhwGS9vGethZpAhYnMejWkX64wsnHPDwvOsKWU6Fg4+AlK2Jz3TyjQLMEPvz+1zemi/WBdkYxCD0bAfIkiw==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "dev": true - }, - "@babel/helper-wrap-function": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.18.6.tgz", - "integrity": "sha512-I5/LZfozwMNbwr/b1vhhuYD+J/mU+gfGAj5td7l5Rv9WYmH6i3Om69WGKNmlIpsVW/mF6O5bvTKbvDQZVgjqOw==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.18.6", - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.6", - "@babel/types": "^7.18.6" - } - }, - "@babel/helpers": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.6.tgz", - "integrity": "sha512-vzSiiqbQOghPngUYt/zWGvK3LAsPhz55vc9XNN0xAl2gV4ieShI2OQli5duxWHD+72PZPTKAcfcZDE1Cwc5zsQ==", - "dev": true, - "requires": { - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.6", - "@babel/types": "^7.18.6" - } - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, - "@babel/parser": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.6.tgz", - "integrity": "sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw==", - "dev": true - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.6.tgz", - "integrity": "sha512-Udgu8ZRgrBrttVz6A0EVL0SJ1z+RLbIeqsu632SA1hf0awEppD6TvdznoH+orIF8wtFFAV/Enmw9Y+9oV8TQcw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.6" - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.6.tgz", - "integrity": "sha512-WAz4R9bvozx4qwf74M+sfqPMKfSqwM0phxPTR6iJIi8robgzXwkEgmeJG1gEKhm6sDqT/U9aV3lfcqybIpev8w==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-class-static-block": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", - "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.6.tgz", - "integrity": "sha512-zr/QcUlUo7GPo6+X1wC98NJADqmy5QTFWWhqeQWiki4XHafJtLl/YMGkmRB2szDD2IYJCCdBTd4ElwhId9T7Xw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.6.tgz", - "integrity": "sha512-zMo66azZth/0tVd7gmkxOkOjs2rpHyhpcFo565PUP37hSp6hSd9uUKIfTDFMz58BwqgQKhJ9YxtM5XddjXVn+Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.6.tgz", - "integrity": "sha512-9yuM6wr4rIsKa1wlUAbZEazkCrgw2sMPEXCr4Rnwetu7cEW1NydkCWytLuYletbf8vFxdJxFhwEZqMpOx2eZyw==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.18.6", - "@babel/helper-compilation-targets": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.18.6" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.6.tgz", - "integrity": "sha512-PatI6elL5eMzoypFAiYDpYQyMtXTn+iMhuxxQt5mAXD4fEmKorpSI3PHd+i3JXBJN3xyA6MvJv7at23HffFHwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.6", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", - "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-import-assertions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz", - "integrity": "sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", - "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", - "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.6.tgz", - "integrity": "sha512-pRqwb91C42vs1ahSAWJkxOxU1RHWDn16XAa6ggQ72wjLlWyYeAcLvTtE0aM8ph3KNydy9CQF2nLYcjq1WysgxQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.6.tgz", - "integrity": "sha512-XTg8XW/mKpzAF3actL554Jl/dOYoJtv3l8fxaEczpgz84IeeVf+T1u2CSvPHuZbt0w3JkIx4rdn/MRQI7mo0HQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.6", - "@babel/helper-function-name": "^7.18.6", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - } - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.6.tgz", - "integrity": "sha512-9repI4BhNrR0KenoR9vm3/cIc1tSBIo+u1WVjKCAynahj25O8zfbiE6JtAtHPGQSs4yZ+bA8mRasRP+qc+2R5A==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.6.tgz", - "integrity": "sha512-tgy3u6lRp17ilY8r1kP4i2+HDUwxlVqq3RTc943eAWSzGgpU1qhiKpqZ5CMyHReIYPHdo3Kg8v8edKtDqSVEyQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.6.tgz", - "integrity": "sha512-NJU26U/208+sxYszf82nmGYqVF9QN8py2HFTblPT9hbawi8+1C5a9JubODLTGFuT0qlkqVinmkwOD13s0sZktg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.6.tgz", - "integrity": "sha512-WAjoMf4wIiSsy88KmG7tgj2nFdEK7E46tArVtcgED7Bkj6Fg/tG5SbvNIOKxbFS2VFgNh6+iaPswBeQZm4ox8w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.6.tgz", - "integrity": "sha512-kJha/Gbs5RjzIu0CxZwf5e3aTTSlhZnHMT8zPWnJMjNpLOUgqevg+PN5oMH68nMCXnfiMo4Bhgxqj59KHTlAnA==", - "dev": true, - "requires": { - "@babel/helper-compilation-targets": "^7.18.6", - "@babel/helper-function-name": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.6.tgz", - "integrity": "sha512-x3HEw0cJZVDoENXOp20HlypIHfl0zMIhMVZEBVTfmqbObIpsMxMbmU5nOEO8R7LYT+z5RORKPlTI5Hj4OsO9/Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz", - "integrity": "sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz", - "integrity": "sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.6.tgz", - "integrity": "sha512-UbPYpXxLjTw6w6yXX2BYNxF3p6QY225wcTkfQCy3OMnSlS/C3xGtwUjEzGkldb/sy6PWLiCQ3NbYfjWUTI3t4g==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz", - "integrity": "sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.6.tgz", - "integrity": "sha512-FjdqgMv37yVl/gwvzkcB+wfjRI8HQmc5EgOG9iGNvUY1ok+TjsoaMP7IqCDZBhkFcM5f3OPVMs6Dmp03C5k4/A==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", - "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "regenerator-transform": "^0.15.0" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.6.tgz", - "integrity": "sha512-ayT53rT/ENF8WWexIRg9AiV9h0aIteyWn5ptfZTZQrjk/+f3WdrJGCY4c9wcgl2+MKkKPhzbYp97FTsquZpDCw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.6" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.6.tgz", - "integrity": "sha512-UuqlRrQmT2SWRvahW46cGSany0uTlcj8NYOS5sRGYi8FxPYPoLd5DDmMd32ZXEj2Jq+06uGVQKHxa/hJx2EzKw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.6.tgz", - "integrity": "sha512-7m71iS/QhsPk85xSjFPovHPcH3H9qeyzsujhTc+vcdnsXavoWYJ74zx0lP5RhpC5+iDnVLO+PPMHzC11qels1g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.6.tgz", - "integrity": "sha512-XNRwQUXYMP7VLuy54cr/KS/WeL3AZeORhrmeZ7iewgu+X2eBqmpaLI/hzqr9ZxCeUoq0ASK4GUzSM0BDhZkLFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/preset-env": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.6.tgz", - "integrity": "sha512-WrthhuIIYKrEFAwttYzgRNQ5hULGmwTj+D6l7Zdfsv5M7IWV/OZbUfbeL++Qrzx1nVJwWROIFhCHRYQV4xbPNw==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.18.6", - "@babel/helper-compilation-targets": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.6", - "@babel/plugin-proposal-async-generator-functions": "^7.18.6", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.6", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.6", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.18.6", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.6", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.18.6", - "@babel/plugin-transform-classes": "^7.18.6", - "@babel/plugin-transform-computed-properties": "^7.18.6", - "@babel/plugin-transform-destructuring": "^7.18.6", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.6", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.6", - "@babel/plugin-transform-function-name": "^7.18.6", - "@babel/plugin-transform-literals": "^7.18.6", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.18.6", - "@babel/plugin-transform-modules-commonjs": "^7.18.6", - "@babel/plugin-transform-modules-systemjs": "^7.18.6", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.18.6", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.18.6", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.18.6", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.6", - "@babel/plugin-transform-typeof-symbol": "^7.18.6", - "@babel/plugin-transform-unicode-escapes": "^7.18.6", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.18.6", - "babel-plugin-polyfill-corejs2": "^0.3.1", - "babel-plugin-polyfill-corejs3": "^0.5.2", - "babel-plugin-polyfill-regenerator": "^0.3.1", - "core-js-compat": "^3.22.1", - "semver": "^6.3.0" - } - }, - "@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/runtime": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.6.tgz", - "integrity": "sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/template": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.6.tgz", - "integrity": "sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.6", - "@babel/types": "^7.18.6" - } - }, - "@babel/traverse": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.6.tgz", - "integrity": "sha512-zS/OKyqmD7lslOtFqbscH6gMLFYOfG1YPqCKfAW5KrTeolKqvB8UelR49Fpr6y93kYkW2Ik00mT1LOGiAGvizw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.6", - "@babel/helper-function-name": "^7.18.6", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.18.6", - "@babel/types": "^7.18.6", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - } - } - }, - "@babel/types": { - "version": "7.18.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.7.tgz", - "integrity": "sha512-QG3yxTcTIBoAcQmkCs+wAPYZhu7Dk9rXKacINfNbdJDNERTbLQbHGyVG8q/YGMPeCJRIhSY0+fTc5+xuh6WPSQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - } - }, - "@codemirror/commands": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.1.2.tgz", - "integrity": "sha512-sO3jdX1s0pam6lIdeSJLMN3DQ6mPEbM4yLvyKkdqtmd/UDwhXA5+AwFJ89rRXm6vTeOXBsE5cAmlos/t7MJdgg==", - "requires": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0" - } - }, - "@codemirror/lang-xml": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.0.0.tgz", - "integrity": "sha512-M/HLWxIiP956xGjtrxkeHkCmDGVQGKu782x8pOH5CLJIMkWtiB1DWfDoDHqpFjdEE9dkfcqPWvYfVi6GbhuXEg==", - "requires": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@lezer/common": "^1.0.0", - "@lezer/xml": "^1.0.0" - }, - "dependencies": { - "@codemirror/autocomplete": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.0.2.tgz", - "integrity": "sha512-9PDjnllmXan/7Uax87KGORbxerDJ/cu10SB+n4Jz0zXMEvIh3+TGgZxhIvDOtaQ4jDBQEM7kHYW4vLdQB0DGZQ==", - "requires": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0" - } - } - } - }, - "@codemirror/language": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.2.1.tgz", - "integrity": "sha512-MC3svxuvIj0MRpFlGHxLS6vPyIdbTr2KKPEW46kCoCXw2ktb4NTkpkPBI/lSP/FoNXLCBJ0mrnUi1OoZxtpW1Q==", - "requires": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0", - "style-mod": "^4.0.0" - } - }, - "@codemirror/state": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.1.2.tgz", - "integrity": "sha512-Mxff85Hp5va+zuj+H748KbubXjrinX/k28lj43H14T2D0+4kuvEFIEIO7hCEcvBT8ubZyIelt9yGOjj2MWOEQA==" - }, - "@codemirror/view": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.0.2.tgz", - "integrity": "sha512-mnVT/q1JvKPjpmjXJNeCi/xHyaJ3abGJsumIVpdQ1nE1MXAyHf7GHWt8QpWMUvDiqF0j+inkhVR2OviTdFFX7Q==", - "requires": { - "@codemirror/state": "^6.0.0", - "style-mod": "^4.0.0", - "w3c-keyname": "^2.2.4" - } - }, - "@commitlint/cli": { - "version": "17.1.2", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-17.1.2.tgz", - "integrity": "sha512-h/4Hlka3bvCLbnxf0Er2ri5A44VMlbMSkdTRp8Adv2tRiklSTRIoPGs7OEXDv3EoDs2AAzILiPookgM4Gi7LOw==", - "dev": true, - "requires": { - "@commitlint/format": "^17.0.0", - "@commitlint/lint": "^17.1.0", - "@commitlint/load": "^17.1.2", - "@commitlint/read": "^17.1.0", - "@commitlint/types": "^17.0.0", - "execa": "^5.0.0", - "lodash": "^4.17.19", - "resolve-from": "5.0.0", - "resolve-global": "1.0.0", - "yargs": "^17.0.0" - }, - "dependencies": { - "yargs": { - "version": "17.3.0", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - } - }, - "yargs-parser": { - "version": "21.0.0", - "dev": true - } - } - }, - "@commitlint/config-conventional": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-17.1.0.tgz", - "integrity": "sha512-WU2p0c9/jLi8k2q2YrDV96Y8XVswQOceIQ/wyJvQxawJSCasLdRB3kUIYdNjOCJsxkpoUlV/b90ZPxp1MYZDiA==", - "dev": true, - "requires": { - "conventional-changelog-conventionalcommits": "^5.0.0" - } - }, - "@commitlint/config-validator": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-17.1.0.tgz", - "integrity": "sha512-Q1rRRSU09ngrTgeTXHq6ePJs2KrI+axPTgkNYDWSJIuS1Op4w3J30vUfSXjwn5YEJHklK3fSqWNHmBhmTR7Vdg==", - "dev": true, - "requires": { - "@commitlint/types": "^17.0.0", - "ajv": "^8.11.0" - } - }, - "@commitlint/ensure": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-17.0.0.tgz", - "integrity": "sha512-M2hkJnNXvEni59S0QPOnqCKIK52G1XyXBGw51mvh7OXDudCmZ9tZiIPpU882p475Mhx48Ien1MbWjCP1zlyC0A==", - "dev": true, - "requires": { - "@commitlint/types": "^17.0.0", - "lodash": "^4.17.19" - } - }, - "@commitlint/execute-rule": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-17.0.0.tgz", - "integrity": "sha512-nVjL/w/zuqjCqSJm8UfpNaw66V9WzuJtQvEnCrK4jDw6qKTmZB+1JQ8m6BQVZbNBcwfYdDNKnhIhqI0Rk7lgpQ==", - "dev": true - }, - "@commitlint/format": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-17.0.0.tgz", - "integrity": "sha512-MZzJv7rBp/r6ZQJDEodoZvdRM0vXu1PfQvMTNWFb8jFraxnISMTnPBWMMjr2G/puoMashwaNM//fl7j8gGV5lA==", - "dev": true, - "requires": { - "@commitlint/types": "^17.0.0", - "chalk": "^4.1.0" - } - }, - "@commitlint/is-ignored": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-17.1.0.tgz", - "integrity": "sha512-JITWKDMHhIh8IpdIbcbuH9rEQJty1ZWelgjleTFrVRAcEwN/sPzk1aVUXRIZNXMJWbZj8vtXRJnFihrml8uECQ==", - "dev": true, - "requires": { - "@commitlint/types": "^17.0.0", - "semver": "7.3.7" - }, - "dependencies": { - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@commitlint/lint": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-17.1.0.tgz", - "integrity": "sha512-ltpqM2ogt/+SDhUaScFo0MdscncEF96lvQTPMM/VTTWlw7sTGLLWkOOppsee2MN/uLNNWjQ7kqkd4h6JqoM9AQ==", - "dev": true, - "requires": { - "@commitlint/is-ignored": "^17.1.0", - "@commitlint/parse": "^17.0.0", - "@commitlint/rules": "^17.0.0", - "@commitlint/types": "^17.0.0" - } - }, - "@commitlint/load": { - "version": "17.1.2", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-17.1.2.tgz", - "integrity": "sha512-sk2p/jFYAWLChIfOIp/MGSIn/WzZ0vkc3afw+l4X8hGEYkvDe4gQUUAVxjl/6xMRn0HgnSLMZ04xXh5pkTsmgg==", - "dev": true, - "requires": { - "@commitlint/config-validator": "^17.1.0", - "@commitlint/execute-rule": "^17.0.0", - "@commitlint/resolve-extends": "^17.1.0", - "@commitlint/types": "^17.0.0", - "@types/node": "^14.0.0", - "chalk": "^4.1.0", - "cosmiconfig": "^7.0.0", - "cosmiconfig-typescript-loader": "^4.0.0", - "lodash": "^4.17.19", - "resolve-from": "^5.0.0", - "ts-node": "^10.8.1", - "typescript": "^4.6.4" - }, - "dependencies": { - "@types/node": { - "version": "14.18.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.26.tgz", - "integrity": "sha512-0b+utRBSYj8L7XAp0d+DX7lI4cSmowNaaTkk6/1SKzbKkG+doLuPusB9EOvzLJ8ahJSk03bTLIL6cWaEd4dBKA==", - "dev": true - } - } - }, - "@commitlint/message": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-17.0.0.tgz", - "integrity": "sha512-LpcwYtN+lBlfZijHUdVr8aNFTVpHjuHI52BnfoV01TF7iSLnia0jttzpLkrLmI8HNQz6Vhr9UrxDWtKZiMGsBw==", - "dev": true - }, - "@commitlint/parse": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-17.0.0.tgz", - "integrity": "sha512-cKcpfTIQYDG1ywTIr5AG0RAiLBr1gudqEsmAGCTtj8ffDChbBRxm6xXs2nv7GvmJN7msOt7vOKleLvcMmRa1+A==", - "dev": true, - "requires": { - "@commitlint/types": "^17.0.0", - "conventional-changelog-angular": "^5.0.11", - "conventional-commits-parser": "^3.2.2" - } - }, - "@commitlint/read": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-17.1.0.tgz", - "integrity": "sha512-73BoFNBA/3Ozo2JQvGsE0J8SdrJAWGfZQRSHqvKaqgmY042Su4gXQLqvAzgr55S9DI1l9TiU/5WDuh8IE86d/g==", - "dev": true, - "requires": { - "@commitlint/top-level": "^17.0.0", - "@commitlint/types": "^17.0.0", - "fs-extra": "^10.0.0", - "git-raw-commits": "^2.0.0", - "minimist": "^1.2.6" - }, - "dependencies": { - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } - } - }, - "@commitlint/resolve-extends": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-17.1.0.tgz", - "integrity": "sha512-jqKm00LJ59T0O8O4bH4oMa4XyJVEOK4GzH8Qye9XKji+Q1FxhZznxMV/bDLyYkzbTodBt9sL0WLql8wMtRTbqQ==", - "dev": true, - "requires": { - "@commitlint/config-validator": "^17.1.0", - "@commitlint/types": "^17.0.0", - "import-fresh": "^3.0.0", - "lodash": "^4.17.19", - "resolve-from": "^5.0.0", - "resolve-global": "^1.0.0" - } - }, - "@commitlint/rules": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-17.0.0.tgz", - "integrity": "sha512-45nIy3dERKXWpnwX9HeBzK5SepHwlDxdGBfmedXhL30fmFCkJOdxHyOJsh0+B0RaVsLGT01NELpfzJUmtpDwdQ==", - "dev": true, - "requires": { - "@commitlint/ensure": "^17.0.0", - "@commitlint/message": "^17.0.0", - "@commitlint/to-lines": "^17.0.0", - "@commitlint/types": "^17.0.0", - "execa": "^5.0.0" - } - }, - "@commitlint/to-lines": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-17.0.0.tgz", - "integrity": "sha512-nEi4YEz04Rf2upFbpnEorG8iymyH7o9jYIVFBG1QdzebbIFET3ir+8kQvCZuBE5pKCtViE4XBUsRZz139uFrRQ==", - "dev": true - }, - "@commitlint/top-level": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-17.0.0.tgz", - "integrity": "sha512-dZrEP1PBJvodNWYPOYiLWf6XZergdksKQaT6i1KSROLdjf5Ai0brLOv5/P+CPxBeoj3vBxK4Ax8H1Pg9t7sHIQ==", - "dev": true, - "requires": { - "find-up": "^5.0.0" - }, - "dependencies": { - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - } - } - }, - "@commitlint/types": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-17.0.0.tgz", - "integrity": "sha512-hBAw6U+SkAT5h47zDMeOu3HSiD0SODw4Aq7rRNh1ceUmL7GyLKYhPbUvlRWqZ65XjBLPHZhFyQlRaPNz8qvUyQ==", - "dev": true, - "requires": { - "chalk": "^4.1.0" - } - }, - "@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "0.3.9" - } - }, - "@csstools/postcss-cascade-layers": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", - "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", - "dev": true, - "requires": { - "@csstools/selector-specificity": "^2.0.2", - "postcss-selector-parser": "^6.0.10" - } - }, - "@csstools/postcss-color-function": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", - "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", - "dev": true, - "requires": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-font-format-keywords": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", - "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-hwb-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", - "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-ic-unit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", - "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", - "dev": true, - "requires": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-is-pseudo-class": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", - "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", - "dev": true, - "requires": { - "@csstools/selector-specificity": "^2.0.0", - "postcss-selector-parser": "^6.0.10" - } - }, - "@csstools/postcss-nested-calc": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", - "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-normalize-display-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", - "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-oklab-function": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", - "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", - "dev": true, - "requires": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-progressive-custom-properties": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", - "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-stepped-value-functions": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", - "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-text-decoration-shorthand": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", - "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-trigonometric-functions": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", - "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-unset-value": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", - "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", - "dev": true, - "requires": {} - }, - "@csstools/selector-specificity": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", - "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", - "dev": true, - "requires": {} - }, - "@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - } - } - }, - "@floating-ui/core": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.0.1.tgz", - "integrity": "sha512-bO37brCPfteXQfFY0DyNDGB3+IMe4j150KFQcgJ5aBP295p9nBGeHEs/p0czrRbtlHq4Px/yoPXO/+dOCcF4uA==" - }, - "@floating-ui/dom": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.0.2.tgz", - "integrity": "sha512-5X9WSvZ8/fjy3gDu8yx9HAA4KG1lazUN2P4/VnaXLxTO9Dz53HI1oYoh1OlhqFNlHgGDiwFX5WhFCc2ljbW3yA==", - "requires": { - "@floating-ui/core": "^1.0.1" - } - }, - "@foliojs-fork/fontkit": { - "version": "1.9.1", - "requires": { - "@foliojs-fork/restructure": "^2.0.2", - "brfs": "^2.0.0", - "brotli": "^1.2.0", - "browserify-optional": "^1.0.1", - "clone": "^1.0.4", - "deep-equal": "^1.0.0", - "dfa": "^1.2.0", - "tiny-inflate": "^1.0.2", - "unicode-properties": "^1.2.2", - "unicode-trie": "^2.0.0" - } - }, - "@foliojs-fork/linebreak": { - "version": "1.1.1", - "requires": { - "base64-js": "1.3.1", - "brfs": "^2.0.2", - "unicode-trie": "^2.0.0" - } - }, - "@foliojs-fork/pdfkit": { - "version": "0.12.3", - "requires": { - "@foliojs-fork/fontkit": "^1.9.1", - "@foliojs-fork/linebreak": "^1.1.1", - "crypto-js": "^4.0.0", - "png-js": "^1.0.0" - } - }, - "@foliojs-fork/restructure": { - "version": "2.0.2" - }, - "@github/clipboard-copy-element": { - "version": "1.1.2" - }, - "@github/hotkey": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@github/hotkey/-/hotkey-2.0.1.tgz", - "integrity": "sha512-qKXjAJjtheJbf4ie3hi8IwrHWJZHB5qdojR6JGo6jvQNPpsdUbk/NIdU8sxu4PW41CjW80vfciDMu3MAP3j2Fg==" - }, - "@github/markdown-toolbar-element": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@github/markdown-toolbar-element/-/markdown-toolbar-element-2.1.1.tgz", - "integrity": "sha512-J++rpd5H9baztabJQB82h26jtueOeBRSTqetk9Cri+Lj/s28ndu6Tovn0uHQaOKtBWDobFunk9b5pP5vcqt7cA==" - }, - "@github/time-elements": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@github/time-elements/-/time-elements-3.1.4.tgz", - "integrity": "sha512-DTe/w0uKVeciKzGtYadNdfS8D86pXdGF+OrKg+vi8PKlotJ45zAc26zNpmmfCcMblBBg2+uoi3OxmUm7am/0sg==" - }, - "@humanwhocodes/config-array": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.5.tgz", - "integrity": "sha512-XVVDtp+dVvRxMoxSiSfasYaG02VEe1qH5cKgMQJWhol6HwzbcqoCMJi8dAGoYAO57jhUyhI6cWuRiTcRaDaYug==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz", - "integrity": "sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@lezer/common": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.0.tgz", - "integrity": "sha512-ohydQe+Hb+w4oMDvXzs8uuJd2NoA3D8YDcLiuDsLqH+yflDTPEpgCsWI3/6rH5C3BAedtH1/R51dxENldQceEA==" - }, - "@lezer/highlight": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.0.0.tgz", - "integrity": "sha512-nsCnNtim90UKsB5YxoX65v3GEIw3iCHw9RM2DtdgkiqAbKh9pCdvi8AWNwkYf10Lu6fxNhXPpkpHbW6mihhvJA==", - "requires": { - "@lezer/common": "^1.0.0" - } - }, - "@lezer/lr": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.1.0.tgz", - "integrity": "sha512-Iad04uVwk1PvSnj25mqj7zEEIRAsasbsTRmVzI0AUTs/+1Dz1//iYAaoLr7A+Xa7bZDfql5MKTxZmSlkYZD3Dg==", - "requires": { - "@lezer/common": "^1.0.0" - } - }, - "@lezer/xml": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.0.tgz", - "integrity": "sha512-73iI9UK8iqSvWtLlOEl/g+50ivwQn8Ge6foHVN66AXUS1RccFnAoc7BYU8b3c8/rP6dfCOGqAGaWLxBzhj60MA==", - "requires": { - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "@lit/reactive-element": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.4.1.tgz", - "integrity": "sha512-qDv4851VFSaBWzpS02cXHclo40jsbAjRXnebNXpm0uVg32kCneZPo9RYVQtrTNICtZ+1wAYHu1ZtxWSWMbKrBw==" - }, - "@nodelib/fs.scandir": { - "version": "2.1.3", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.3", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.3", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.4", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.3", - "fastq": "^1.6.0" - } - }, - "@octokit/auth-token": { - "version": "2.5.0", - "dev": true, - "requires": { - "@octokit/types": "^6.0.3" - } - }, - "@octokit/core": { - "version": "3.5.1", - "dev": true, - "requires": { - "@octokit/auth-token": "^2.4.4", - "@octokit/graphql": "^4.5.8", - "@octokit/request": "^5.6.0", - "@octokit/request-error": "^2.0.5", - "@octokit/types": "^6.0.3", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/endpoint": { - "version": "6.0.12", - "dev": true, - "requires": { - "@octokit/types": "^6.0.3", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/graphql": { - "version": "4.8.0", - "dev": true, - "requires": { - "@octokit/request": "^5.6.0", - "@octokit/types": "^6.0.3", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/openapi-types": { - "version": "11.2.0", - "dev": true - }, - "@octokit/plugin-paginate-rest": { - "version": "2.17.0", - "dev": true, - "requires": { - "@octokit/types": "^6.34.0" - } - }, - "@octokit/plugin-request-log": { - "version": "1.0.4", - "dev": true, - "requires": {} - }, - "@octokit/plugin-rest-endpoint-methods": { - "version": "5.13.0", - "dev": true, - "requires": { - "@octokit/types": "^6.34.0", - "deprecation": "^2.3.1" - } - }, - "@octokit/request": { - "version": "5.6.2", - "dev": true, - "requires": { - "@octokit/endpoint": "^6.0.1", - "@octokit/request-error": "^2.1.0", - "@octokit/types": "^6.16.1", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.1", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/request-error": { - "version": "2.1.0", - "dev": true, - "requires": { - "@octokit/types": "^6.0.3", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - }, - "@octokit/rest": { - "version": "18.12.0", - "dev": true, - "requires": { - "@octokit/core": "^3.5.1", - "@octokit/plugin-paginate-rest": "^2.16.8", - "@octokit/plugin-request-log": "^1.0.4", - "@octokit/plugin-rest-endpoint-methods": "^5.12.0" - } - }, - "@octokit/types": { - "version": "6.34.0", - "dev": true, - "requires": { - "@octokit/openapi-types": "^11.2.0" - } - }, - "@rollup/plugin-babel": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", - "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@rollup/pluginutils": "^3.1.0" - } - }, - "@rollup/plugin-node-resolve": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", - "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.1.0", - "@types/resolve": "1.17.1", - "builtin-modules": "^3.1.0", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.19.0" - } - }, - "@rollup/plugin-replace": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", - "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.1.0", - "magic-string": "^0.25.7" - }, - "dependencies": { - "magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.8" - } - } - } - }, - "@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "requires": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - } - }, - "@semantic-release/changelog": { - "version": "6.0.1", - "dev": true, - "requires": { - "@semantic-release/error": "^3.0.0", - "aggregate-error": "^3.0.0", - "fs-extra": "^9.0.0", - "lodash": "^4.17.4" - } - }, - "@semantic-release/commit-analyzer": { - "version": "9.0.2", - "dev": true, - "requires": { - "conventional-changelog-angular": "^5.0.0", - "conventional-commits-filter": "^2.0.0", - "conventional-commits-parser": "^3.2.3", - "debug": "^4.0.0", - "import-from": "^4.0.0", - "lodash": "^4.17.4", - "micromatch": "^4.0.2" - }, - "dependencies": { - "import-from": { - "version": "4.0.0", - "dev": true - } - } - }, - "@semantic-release/error": { - "version": "3.0.0", - "dev": true - }, - "@semantic-release/exec": { - "version": "6.0.3", - "dev": true, - "requires": { - "@semantic-release/error": "^3.0.0", - "aggregate-error": "^3.0.0", - "debug": "^4.0.0", - "execa": "^5.0.0", - "lodash": "^4.17.4", - "parse-json": "^5.0.0" - }, - "dependencies": { - "parse-json": { - "version": "5.1.0", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - } - } - }, - "@semantic-release/git": { - "version": "10.0.1", - "dev": true, - "requires": { - "@semantic-release/error": "^3.0.0", - "aggregate-error": "^3.0.0", - "debug": "^4.0.0", - "dir-glob": "^3.0.0", - "execa": "^5.0.0", - "lodash": "^4.17.4", - "micromatch": "^4.0.0", - "p-reduce": "^2.0.0" - } - }, - "@semantic-release/github": { - "version": "8.0.2", - "dev": true, - "requires": { - "@octokit/rest": "^18.0.0", - "@semantic-release/error": "^2.2.0", - "aggregate-error": "^3.0.0", - "bottleneck": "^2.18.1", - "debug": "^4.0.0", - "dir-glob": "^3.0.0", - "fs-extra": "^10.0.0", - "globby": "^11.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "issue-parser": "^6.0.0", - "lodash": "^4.17.4", - "mime": "^3.0.0", - "p-filter": "^2.0.0", - "p-retry": "^4.0.0", - "url-join": "^4.0.0" - }, - "dependencies": { - "@semantic-release/error": { - "version": "2.2.0", - "dev": true - }, - "@tootallnate/once": { - "version": "2.0.0", - "dev": true - }, - "fs-extra": { - "version": "10.0.0", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "http-proxy-agent": { - "version": "5.0.0", - "dev": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, - "universalify": { - "version": "2.0.0", - "dev": true - } - } - }, - "@semantic-release/gitlab": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/@semantic-release/gitlab/-/gitlab-9.4.2.tgz", - "integrity": "sha512-ZfMe4N613C/tqfRdxDPprOoBA7HHyHFHvk+TA4HqOmNpgUKiifyJPpYJsc3PLNtec9I0TA1OOTuY82VDG+FVXw==", - "dev": true, - "requires": { - "@semantic-release/error": "^3.0.0", - "aggregate-error": "^3.0.0", - "debug": "^4.0.0", - "dir-glob": "^3.0.0", - "escape-string-regexp": "^3.0.0", - "form-data": "^4.0.0", - "fs-extra": "^10.0.0", - "globby": "^11.0.0", - "got": "^11.0.0", - "hpagent": "^1.0.0", - "lodash": "^4.17.11", - "parse-url": "^8.0.0", - "url-join": "^4.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "3.0.0", - "dev": true - }, - "form-data": { - "version": "4.0.0", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fs-extra": { - "version": "10.0.0", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "dev": true - } - } - }, - "@semantic-release/npm": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-9.0.0.tgz", - "integrity": "sha512-hj2jqayS2SPUmFtCMCOQMX975uMDfRoymj1HvMSwYdaoI6hVZvhrTFPBgJeM85O0C+G3IFviAUar5gel/1VGDQ==", - "dev": true, - "requires": { - "@semantic-release/error": "^3.0.0", - "aggregate-error": "^3.0.0", - "execa": "^5.0.0", - "fs-extra": "^10.0.0", - "lodash": "^4.17.15", - "nerf-dart": "^1.0.0", - "normalize-url": "^6.0.0", - "npm": "^8.3.0", - "rc": "^1.2.8", - "read-pkg": "^5.0.0", - "registry-auth-token": "^4.0.0", - "semver": "^7.1.2", - "tempy": "^1.0.0" - }, - "dependencies": { - "fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } - } - }, - "@semantic-release/release-notes-generator": { - "version": "10.0.3", - "dev": true, - "requires": { - "conventional-changelog-angular": "^5.0.0", - "conventional-changelog-writer": "^5.0.0", - "conventional-commits-filter": "^2.0.0", - "conventional-commits-parser": "^3.2.3", - "debug": "^4.0.0", - "get-stream": "^6.0.0", - "import-from": "^4.0.0", - "into-stream": "^6.0.0", - "lodash": "^4.17.4", - "read-pkg-up": "^7.0.0" - }, - "dependencies": { - "get-stream": { - "version": "6.0.1", - "dev": true - }, - "import-from": { - "version": "4.0.0", - "dev": true - } - } - }, - "@sindresorhus/is": { - "version": "4.2.0", - "dev": true - }, - "@stencil/core": { - "version": "2.5.2" - }, - "@surma/rollup-plugin-off-main-thread": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", - "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", - "dev": true, - "requires": { - "ejs": "^3.1.6", - "json5": "^2.2.0", - "magic-string": "^0.25.0", - "string.prototype.matchall": "^4.0.6" - } - }, - "@szmarczak/http-timer": { - "version": "4.0.6", - "dev": true, - "requires": { - "defer-to-connect": "^2.0.0" - } - }, - "@tailwindcss/forms": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.3.tgz", - "integrity": "sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==", - "dev": true, - "requires": { - "mini-svg-data-uri": "^1.2.3" - } - }, - "@tailwindcss/line-clamp": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/line-clamp/-/line-clamp-0.4.2.tgz", - "integrity": "sha512-HFzAQuqYCjyy/SX9sLGB1lroPzmcnWv1FHkIpmypte10hptf4oPUfucryMKovZh2u0uiS9U5Ty3GghWfEJGwVw==", - "dev": true, - "requires": {} - }, - "@tailwindcss/nesting": { - "version": "0.0.0-insiders.565cd3e", - "resolved": "https://registry.npmjs.org/@tailwindcss/nesting/-/nesting-0.0.0-insiders.565cd3e.tgz", - "integrity": "sha512-WhHoFBx19TnH/c+xLwT/sxei6+4RpdfiyG3MYXfmLaMsADmVqBkF7B6lDalgZD9YdM459MF7DtxVbWkOrV7IaQ==", - "requires": { - "postcss-nested": "^5.0.5" - } - }, - "@tailwindcss/typography": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.7.tgz", - "integrity": "sha512-JTTSTrgZfp6Ki4svhPA4mkd9nmQ/j9EfE7SbHJ1cLtthKkpW2OxsFXzSmxbhYbEkfNIyAyhle5p4SYyKRbz/jg==", - "dev": true, - "requires": { - "lodash.castarray": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.merge": "^4.6.2", - "postcss-selector-parser": "6.0.10" - } - }, - "@tootallnate/once": { - "version": "1.1.2" - }, - "@trysound/sax": { - "version": "0.2.0", - "dev": true - }, - "@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true - }, - "@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true - }, - "@types/cacheable-request": { - "version": "6.0.2", - "dev": true, - "requires": { - "@types/http-cache-semantics": "*", - "@types/keyv": "*", - "@types/node": "*", - "@types/responselike": "*" - } - }, - "@types/color-name": { - "version": "1.1.1", - "dev": true - }, - "@types/debounce": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-epMsEE85fi4lfmJUH/89/iV/LI+F5CvNIvmgs5g5jYFPfhO2S/ae8WSsLOKWdwtoaZw9Q2IhJ4tQ5tFCcS/4HA==", - "dev": true - }, - "@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, - "@types/fscreen": { - "version": "1.0.1" - }, - "@types/geojson": { - "version": "7946.0.8", - "dev": true - }, - "@types/http-cache-semantics": { - "version": "4.0.1", - "dev": true - }, - "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "@types/keyv": { - "version": "3.1.3", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/leaflet": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.8.0.tgz", - "integrity": "sha512-+sXFmiJTFdhaXXIGFlV5re9AdqtAODoXbGAvxx02e5SHXL3ir7ClP5J7pahO8VmzKY3dth4RUS1nf2BTT+DW1A==", - "dev": true, - "requires": { - "@types/geojson": "*" - } - }, - "@types/marked": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.7.tgz", - "integrity": "sha512-eEAhnz21CwvKVW+YvRvcTuFKNU9CV1qH+opcgVK3pIMI6YZzDm6gc8o2vHjldFk6MGKt5pueSB7IOpvpx5Qekw==", - "dev": true - }, - "@types/minimist": { - "version": "1.2.1", - "dev": true - }, - "@types/node": { - "version": "17.0.5", - "dev": true - }, - "@types/normalize-package-data": { - "version": "2.4.0", - "dev": true - }, - "@types/parse-json": { - "version": "4.0.0", - "dev": true - }, - "@types/resolve": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", - "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/responselike": { - "version": "1.0.0", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/retry": { - "version": "0.12.1", - "dev": true - }, - "@types/trusted-types": { - "version": "2.0.2" - }, - "@types/wavesurfer.js": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/wavesurfer.js/-/wavesurfer.js-6.0.3.tgz", - "integrity": "sha512-5Sb5s3pEkOmDosaaP1DWp1Unnx8HhVorm5608TIVdT5jCMvJ6eqM19UD8n7DEbJ7rzreS9RqHmzR8TlcdQBvbA==", - "dev": true, - "requires": { - "@types/debounce": "*" - } - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.40.0.tgz", - "integrity": "sha512-FIBZgS3DVJgqPwJzvZTuH4HNsZhHMa9SjxTKAZTlMsPw/UzpEjcf9f4dfgDJEHjK+HboUJo123Eshl6niwEm/Q==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.40.0", - "@typescript-eslint/type-utils": "5.40.0", - "@typescript-eslint/utils": "5.40.0", - "debug": "^4.3.4", - "ignore": "^5.2.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/parser": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.40.0.tgz", - "integrity": "sha512-Ah5gqyX2ySkiuYeOIDg7ap51/b63QgWZA7w6AHtFrag7aH0lRQPbLzUjk0c9o5/KZ6JRkTTDKShL4AUrQa6/hw==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.40.0", - "@typescript-eslint/types": "5.40.0", - "@typescript-eslint/typescript-estree": "5.40.0", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.40.0.tgz", - "integrity": "sha512-d3nPmjUeZtEWRvyReMI4I1MwPGC63E8pDoHy0BnrYjnJgilBD3hv7XOiETKLY/zTwI7kCnBDf2vWTRUVpYw0Uw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.40.0", - "@typescript-eslint/visitor-keys": "5.40.0" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.40.0.tgz", - "integrity": "sha512-nfuSdKEZY2TpnPz5covjJqav+g5qeBqwSHKBvz7Vm1SAfy93SwKk/JeSTymruDGItTwNijSsno5LhOHRS1pcfw==", - "dev": true, - "requires": { - "@typescript-eslint/typescript-estree": "5.40.0", - "@typescript-eslint/utils": "5.40.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/types": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.40.0.tgz", - "integrity": "sha512-V1KdQRTXsYpf1Y1fXCeZ+uhjW48Niiw0VGt4V8yzuaDTU8Z1Xl7yQDyQNqyAFcVhpYXIVCEuxSIWTsLDpHgTbw==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.40.0.tgz", - "integrity": "sha512-b0GYlDj8TLTOqwX7EGbw2gL5EXS2CPEWhF9nGJiGmEcmlpNBjyHsTwbqpyIEPVpl6br4UcBOYlcI2FJVtJkYhg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.40.0", - "@typescript-eslint/visitor-keys": "5.40.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/utils": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.40.0.tgz", - "integrity": "sha512-MO0y3T5BQ5+tkkuYZJBjePewsY+cQnfkYeRqS6tPh28niiIwPnQ1t59CSRcs1ZwJJNOdWw7rv9pF8aP58IMihA==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.40.0", - "@typescript-eslint/types": "5.40.0", - "@typescript-eslint/typescript-estree": "5.40.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", - "semver": "^7.3.7" - }, - "dependencies": { - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.40.0.tgz", - "integrity": "sha512-ijJ+6yig+x9XplEpG2K6FUdJeQGGj/15U3S56W9IqXKJqleuD7zJ2AX/miLezwxpd7ZxDAqO87zWufKg+RPZyQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.40.0", - "eslint-visitor-keys": "^3.3.0" - } - }, - "@vime/core": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@vime/core/-/core-5.3.3.tgz", - "integrity": "sha512-bGn6bwiOwI/0d+P+gLH9U3ySK4tar6Qiv7g+RR+c7iuCsl0S16/wP1G677PiglJTvM3wuo2wD3hztMEkiWfvhw==", - "requires": { - "@stencil/core": "2.5.2", - "@types/fscreen": "^1.0.1", - "fscreen": "^1.2.0", - "mitt": "^3.0.0", - "stencil-wormhole": "^3.4.1" - } - }, - "abab": { - "version": "2.0.5" - }, - "acorn": { - "version": "7.4.1" - }, - "acorn-globals": { - "version": "6.0.0", - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "acorn-node": { - "version": "1.8.2", - "requires": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "acorn-walk": { - "version": "7.2.0" - }, - "adler-32": { - "version": "1.2.0", - "requires": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.1.0" - } - }, - "agent-base": { - "version": "6.0.2", - "requires": { - "debug": "4" - } - }, - "aggregate-error": { - "version": "3.1.0", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "all-contributors-cli": { - "version": "6.24.0", - "resolved": "https://registry.npmjs.org/all-contributors-cli/-/all-contributors-cli-6.24.0.tgz", - "integrity": "sha512-7oSKr2PnqxsOotuSwciltcFTS1eVRdjR0cn99hbElfff7gRQBShVhsf/XBprY41sLcgqTk0l0MKgKv6QNgZdMg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.6", - "async": "^3.1.0", - "chalk": "^4.0.0", - "didyoumean": "^1.2.1", - "inquirer": "^7.3.3", - "json-fixer": "^1.6.8", - "lodash": "^4.11.2", - "node-fetch": "^2.6.0", - "pify": "^5.0.0", - "yargs": "^15.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - } - }, - "pify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", - "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", - "dev": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "amdefine": { - "version": "1.0.1", - "optional": true - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - } - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "ansicolors": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", - "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", - "dev": true - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "argv-formatter": { - "version": "1.0.0", - "dev": true - }, - "array-from": { - "version": "2.1.1" - }, - "array-ify": { - "version": "1.0.0", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "dev": true - }, - "ast-transform": { - "version": "0.0.0", - "requires": { - "escodegen": "~1.2.0", - "esprima": "~1.0.4", - "through": "~2.3.4" - }, - "dependencies": { - "esprima": { - "version": "1.0.4" - } - } - }, - "ast-types": { - "version": "0.7.8" - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0" - }, - "at-least-node": { - "version": "1.0.0", - "dev": true - }, - "autoprefixer": { - "version": "10.4.12", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.12.tgz", - "integrity": "sha512-WrCGV9/b97Pa+jtwf5UGaRjgQIg7OK3D06GnoYoZNcG1Xb8Gt3EfuKjlhh9i/VtT16g6PYjZ69jdJ2g8FxSC4Q==", - "dev": true, - "requires": { - "browserslist": "^4.21.4", - "caniuse-lite": "^1.0.30001407", - "fraction.js": "^4.2.0", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - } - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "requires": { - "object.assign": "^4.1.0" - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", - "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.3.1", - "semver": "^6.1.1" - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", - "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.1", - "core-js-compat": "^3.21.0" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", - "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.1" - } - }, - "babel-runtime": { - "version": "6.26.0", - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "core-js": { - "version": "2.6.12" - }, - "regenerator-runtime": { - "version": "0.11.1" - } - } - }, - "balanced-match": { - "version": "1.0.0", - "dev": true - }, - "base64-js": { - "version": "1.3.1" - }, - "before-after-hook": { - "version": "2.2.2", - "dev": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "boolbase": { - "version": "1.0.0", - "dev": true - }, - "bottleneck": { - "version": "2.19.5", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "brfs": { - "version": "2.0.2", - "requires": { - "quote-stream": "^1.0.1", - "resolve": "^1.1.5", - "static-module": "^3.0.2", - "through2": "^2.0.0" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, - "brotli": { - "version": "1.3.2", - "requires": { - "base64-js": "^1.1.2" - } - }, - "browser-process-hrtime": { - "version": "1.0.0" - }, - "browser-resolve": { - "version": "1.11.3", - "requires": { - "resolve": "1.1.7" - }, - "dependencies": { - "resolve": { - "version": "1.1.7" - } - } - }, - "browserify-optional": { - "version": "1.0.1", - "requires": { - "ast-transform": "0.0.0", - "ast-types": "^0.7.0", - "browser-resolve": "^1.8.1" - } - }, - "browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-equal": { - "version": "0.0.1" - }, - "buffer-from": { - "version": "1.1.2" - }, - "builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true - }, - "cacheable-lookup": { - "version": "5.0.4", - "dev": true - }, - "cacheable-request": { - "version": "7.0.2", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - } - }, - "cachedir": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", - "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "camelcase": { - "version": "5.3.1", - "dev": true - }, - "camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true - }, - "camelcase-keys": { - "version": "6.2.2", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - } - }, - "caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "caniuse-lite": { - "version": "1.0.30001412", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001412.tgz", - "integrity": "sha512-+TeEIee1gS5bYOiuf+PS/kp2mrXic37Hl66VY6EAfxasIk5fELTktK2oOezYed12H8w7jt3s512PpulQidPjwA==", - "dev": true - }, - "cardinal": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", - "integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=", - "dev": true, - "requires": { - "ansicolors": "~0.3.2", - "redeyed": "~2.1.0" - } - }, - "cfb": { - "version": "1.2.1", - "requires": { - "adler-32": "~1.3.0", - "crc-32": "~1.2.0", - "printj": "~1.3.0" - }, - "dependencies": { - "adler-32": { - "version": "1.3.0", - "requires": { - "printj": "~1.2.2" - }, - "dependencies": { - "printj": { - "version": "1.2.3" - } - } - }, - "printj": { - "version": "1.3.0" - } - } - }, - "chalk": { - "version": "4.1.2", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "chardet": { - "version": "0.7.0", - "dev": true - }, - "choices.js": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/choices.js/-/choices.js-10.1.0.tgz", - "integrity": "sha512-NtrFt7c7ZQEGmkWsAV+EHynJhADWoZ82JEfg1+vQ9MMKJD4Ax2rzYPxXe+Q64i0HgUgWG/XTN3gN2pB8UFFFlA==", - "requires": { - "deepmerge": "^4.2.2", - "fuse.js": "^6.5.3", - "redux": "^4.1.2" - } - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "ci-info": { - "version": "3.3.0", - "dev": true - }, - "clean-stack": { - "version": "2.2.0", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-spinners": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", - "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", - "dev": true - }, - "cli-table3": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz", - "integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==", - "dev": true, - "requires": { - "colors": "1.4.0", - "string-width": "^4.2.0" - } - }, - "cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "requires": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - } - }, - "cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true - }, - "cliui": { - "version": "7.0.4", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "clone": { - "version": "1.0.4" - }, - "clone-response": { - "version": "1.0.2", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "codemirror": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", - "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", - "requires": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/commands": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/lint": "^6.0.0", - "@codemirror/search": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0" - }, - "dependencies": { - "@codemirror/autocomplete": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.0.2.tgz", - "integrity": "sha512-9PDjnllmXan/7Uax87KGORbxerDJ/cu10SB+n4Jz0zXMEvIh3+TGgZxhIvDOtaQ4jDBQEM7kHYW4vLdQB0DGZQ==", - "requires": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0" - } - }, - "@codemirror/lint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.0.0.tgz", - "integrity": "sha512-nUUXcJW1Xp54kNs+a1ToPLK8MadO0rMTnJB8Zk4Z8gBdrN0kqV7uvUraU/T2yqg+grDNR38Vmy/MrhQN/RgwiA==", - "requires": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "crelt": "^1.0.5" - } - }, - "@codemirror/search": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.0.0.tgz", - "integrity": "sha512-rL0rd3AhI0TAsaJPUaEwC63KHLO7KL0Z/dYozXj6E7L3wNHRyx7RfE0/j5HsIf912EE5n2PCb4Vg0rGYmDv4UQ==", - "requires": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "crelt": "^1.0.5" - } - } - } - }, - "codepage": { - "version": "1.15.0" - }, - "color-convert": { - "version": "1.9.3", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "dev": true - }, - "colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true - }, - "colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", - "dev": true - }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "optional": true - }, - "combined-stream": { - "version": "1.0.8", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.3.0.tgz", - "integrity": "sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw==", - "dev": true - }, - "commitizen": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-4.2.5.tgz", - "integrity": "sha512-9sXju8Qrz1B4Tw7kC5KhnvwYQN88qs2zbiB8oyMsnXZyJ24PPGiNM3nHr73d32dnE3i8VJEXddBFIbOgYSEXtQ==", - "dev": true, - "requires": { - "cachedir": "2.3.0", - "cz-conventional-changelog": "3.3.0", - "dedent": "0.7.0", - "detect-indent": "6.1.0", - "find-node-modules": "^2.1.2", - "find-root": "1.1.0", - "fs-extra": "9.1.0", - "glob": "7.2.3", - "inquirer": "8.2.4", - "is-utf8": "^0.2.1", - "lodash": "4.17.21", - "minimist": "1.2.6", - "strip-bom": "4.0.0", - "strip-json-comments": "3.1.1" - }, - "dependencies": { - "find-node-modules": { - "version": "2.1.2", - "dev": true, - "requires": { - "findup-sync": "^4.0.0", - "merge": "^2.1.0" - } - }, - "findup-sync": { - "version": "4.0.0", - "dev": true, - "requires": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^4.0.2", - "resolve-dir": "^1.0.1" - } - }, - "merge": { - "version": "2.1.1", - "dev": true - } - } - }, - "common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "dev": true - }, - "compare-func": { - "version": "2.0.0", - "dev": true, - "requires": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" - } - }, - "concat-map": { - "version": "0.0.1", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "conventional-changelog-angular": { - "version": "5.0.13", - "dev": true, - "requires": { - "compare-func": "^2.0.0", - "q": "^1.5.1" - } - }, - "conventional-changelog-conventionalcommits": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-5.0.0.tgz", - "integrity": "sha512-lCDbA+ZqVFQGUj7h9QBKoIpLhl8iihkO0nCTyRNzuXtcd7ubODpYB04IFy31JloiJgG0Uovu8ot8oxRzn7Nwtw==", - "dev": true, - "requires": { - "compare-func": "^2.0.0", - "lodash": "^4.17.15", - "q": "^1.5.1" - } - }, - "conventional-changelog-writer": { - "version": "5.0.0", - "dev": true, - "requires": { - "conventional-commits-filter": "^2.0.7", - "dateformat": "^3.0.0", - "handlebars": "^4.7.6", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "semver": "^6.0.0", - "split": "^1.0.0", - "through2": "^4.0.0" - } - }, - "conventional-commit-types": { - "version": "3.0.0", - "dev": true - }, - "conventional-commits-filter": { - "version": "2.0.7", - "dev": true, - "requires": { - "lodash.ismatch": "^4.4.0", - "modify-values": "^1.0.0" - } - }, - "conventional-commits-parser": { - "version": "3.2.3", - "dev": true, - "requires": { - "is-text-path": "^1.0.1", - "JSONStream": "^1.0.4", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - } - }, - "convert-source-map": { - "version": "1.7.0", - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "core-js": { - "version": "3.6.5" - }, - "core-js-compat": { - "version": "3.23.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.23.3.tgz", - "integrity": "sha512-WSzUs2h2vvmKsacLHNTdpyOC9k43AEhcGoFlVgCY4L7aw98oSBKtPL6vD0/TqZjRWRQYdDSLkzZIni4Crbbiqw==", - "dev": true, - "requires": { - "browserslist": "^4.21.0", - "semver": "7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true - } - } - }, - "core-util-is": { - "version": "1.0.2" - }, - "cosmiconfig": { - "version": "7.0.1", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "dependencies": { - "parse-json": { - "version": "5.2.0", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - } - } - }, - "cosmiconfig-typescript-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.0.0.tgz", - "integrity": "sha512-cVpucSc2Tf+VPwCCR7SZzmQTQkPbkk4O01yXsYqXBIbjE1bhwqSyAgYQkRK1un4i0OPziTleqFhdkmOc4RQ/9g==", - "dev": true, - "requires": {} - }, - "crc-32": { - "version": "1.2.0", - "requires": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.1.0" - } - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "crelt": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.5.tgz", - "integrity": "sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==" - }, - "cross-env": { - "version": "7.0.3", - "dev": true, - "requires": { - "cross-spawn": "^7.0.1" - } - }, - "cross-spawn": { - "version": "7.0.3", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "dependencies": { - "which": { - "version": "2.0.2", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "crypto-js": { - "version": "4.1.1" - }, - "crypto-random-string": { - "version": "2.0.0", - "dev": true - }, - "css-blank-pseudo": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", - "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.9" - } - }, - "css-declaration-sorter": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.0.tgz", - "integrity": "sha512-OGT677UGHJTAVMRhPO+HJ4oKln3wkBTwtDFH0ojbqm+MJm6xuDMHp2nkhh/ThaBqq20IbraBQSWKfSLNHQO9Og==", - "dev": true, - "requires": {} - }, - "css-functions-list": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.1.0.tgz", - "integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==", - "dev": true - }, - "css-has-pseudo": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", - "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.9" - } - }, - "css-prefers-color-scheme": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", - "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", - "dev": true, - "requires": {} - }, - "css-tree": { - "version": "1.1.3", - "dev": true, - "requires": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "dev": true - } - } - }, - "cssdb": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.0.1.tgz", - "integrity": "sha512-pT3nzyGM78poCKLAEy2zWIVX2hikq6dIrjuZzLV98MumBg+xMTNYfHx7paUlfiRTgg91O/vR889CIf+qiv79Rw==", - "dev": true - }, - "cssesc": { - "version": "3.0.0" - }, - "cssnano": { - "version": "5.1.13", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.13.tgz", - "integrity": "sha512-S2SL2ekdEz6w6a2epXn4CmMKU4K3KpcyXLKfAYc9UQQqJRkD/2eLUG0vJ3Db/9OvO5GuAdgXw3pFbR6abqghDQ==", - "dev": true, - "requires": { - "cssnano-preset-default": "^5.2.12", - "lilconfig": "^2.0.3", - "yaml": "^1.10.2" - } - }, - "cssnano-preset-default": { - "version": "5.2.12", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.12.tgz", - "integrity": "sha512-OyCBTZi+PXgylz9HAA5kHyoYhfGcYdwFmyaJzWnzxuGRtnMw/kR6ilW9XzlzlRAtB6PLT/r+prYgkef7hngFew==", - "dev": true, - "requires": { - "css-declaration-sorter": "^6.3.0", - "cssnano-utils": "^3.1.0", - "postcss-calc": "^8.2.3", - "postcss-colormin": "^5.3.0", - "postcss-convert-values": "^5.1.2", - "postcss-discard-comments": "^5.1.2", - "postcss-discard-duplicates": "^5.1.0", - "postcss-discard-empty": "^5.1.1", - "postcss-discard-overridden": "^5.1.0", - "postcss-merge-longhand": "^5.1.6", - "postcss-merge-rules": "^5.1.2", - "postcss-minify-font-values": "^5.1.0", - "postcss-minify-gradients": "^5.1.1", - "postcss-minify-params": "^5.1.3", - "postcss-minify-selectors": "^5.2.1", - "postcss-normalize-charset": "^5.1.0", - "postcss-normalize-display-values": "^5.1.0", - "postcss-normalize-positions": "^5.1.1", - "postcss-normalize-repeat-style": "^5.1.1", - "postcss-normalize-string": "^5.1.0", - "postcss-normalize-timing-functions": "^5.1.0", - "postcss-normalize-unicode": "^5.1.0", - "postcss-normalize-url": "^5.1.0", - "postcss-normalize-whitespace": "^5.1.1", - "postcss-ordered-values": "^5.1.3", - "postcss-reduce-initial": "^5.1.0", - "postcss-reduce-transforms": "^5.1.0", - "postcss-svgo": "^5.1.0", - "postcss-unique-selectors": "^5.1.1" - } - }, - "cssnano-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", - "dev": true, - "requires": {} - }, - "csso": { - "version": "4.2.0", - "dev": true, - "requires": { - "css-tree": "^1.1.2" - } - }, - "cssom": { - "version": "0.4.4" - }, - "cssstyle": { - "version": "2.3.0", - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8" - } - } - }, - "cz-conventional-changelog": { - "version": "3.3.0", - "dev": true, - "requires": { - "@commitlint/load": ">6.1.1", - "chalk": "^2.4.1", - "commitizen": "^4.0.3", - "conventional-commit-types": "^3.0.0", - "lodash.map": "^4.5.1", - "longest": "^2.0.1", - "word-wrap": "^1.0.3" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, - "d": { - "version": "1.0.1", - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "d3-array": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", - "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", - "requires": { - "internmap": "1 - 2" - } - }, - "d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==" - }, - "d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==" - }, - "d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==" - }, - "d3-force": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "requires": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - } - }, - "d3-geo": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.0.1.tgz", - "integrity": "sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA==", - "requires": { - "d3-array": "2.5.0 - 3" - } - }, - "d3-geo-projection": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz", - "integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==", - "requires": { - "commander": "7", - "d3-array": "1 - 3", - "d3-geo": "1.12.0 - 3" - }, - "dependencies": { - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" - } - } - }, - "d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "requires": { - "d3-color": "1 - 3" - } - }, - "d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==" - }, - "d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==" - }, - "d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==" - }, - "d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "requires": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - } - }, - "dargs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", - "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", - "dev": true - }, - "dash-ast": { - "version": "1.0.0" - }, - "data-urls": { - "version": "2.0.0", - "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - } - }, - "dateformat": { - "version": "3.0.3", - "dev": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "1.2.0", - "dev": true - }, - "decamelize-keys": { - "version": "1.1.0", - "dev": true, - "requires": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "map-obj": { - "version": "1.0.1", - "dev": true - } - } - }, - "decimal.js": { - "version": "10.3.1" - }, - "decompress-response": { - "version": "6.0.0", - "dev": true, - "requires": { - "mimic-response": "^3.1.0" - }, - "dependencies": { - "mimic-response": { - "version": "3.1.0", - "dev": true - } - } - }, - "dedent": { - "version": "0.7.0", - "dev": true - }, - "deep-equal": { - "version": "1.1.1", - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, - "deep-is": { - "version": "0.1.3" - }, - "deepmerge": { - "version": "4.2.2" - }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==", - "dev": true, - "requires": { - "clone": "^1.0.2" - } - }, - "defer-to-connect": { - "version": "2.0.1", - "dev": true - }, - "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==", - "dev": true - }, - "del": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", - "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", - "dev": true, - "requires": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - } - }, - "delayed-stream": { - "version": "1.0.0" - }, - "deprecation": { - "version": "2.3.1", - "dev": true - }, - "detect-file": { - "version": "1.0.0", - "dev": true - }, - "detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "dev": true - }, - "detective": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", - "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", - "dev": true, - "requires": { - "acorn-node": "^1.8.2", - "defined": "^1.0.0", - "minimist": "^1.2.6" - } - }, - "dfa": { - "version": "1.2.0" - }, - "didyoumean": { - "version": "1.2.2", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "dlv": { - "version": "1.1.3", - "dev": true - }, - "doctrine": { - "version": "3.0.0", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "domexception": { - "version": "2.0.1", - "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0" - } - } - }, - "dot-prop": { - "version": "5.3.0", - "dev": true, - "requires": { - "is-obj": "^2.0.0" - } - }, - "duplexer2": { - "version": "0.1.4", - "requires": { - "readable-stream": "^2.0.2" - } - }, - "ejs": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", - "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==", - "dev": true, - "requires": { - "jake": "^10.8.5" - } - }, - "electron-to-chromium": { - "version": "1.4.264", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.264.tgz", - "integrity": "sha512-AZ6ZRkucHOQT8wke50MktxtmcWZr67kE17X/nAXFf62NIdMdgY6xfsaJD5Szoy84lnkuPWH+4tTNE3s2+bPCiw==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "dev": true - }, - "end-of-stream": { - "version": "1.4.4", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "entities": { - "version": "2.2.0", - "dev": true - }, - "env-ci": { - "version": "5.4.1", - "dev": true, - "requires": { - "execa": "^5.0.0", - "fromentries": "^1.3.2", - "java-properties": "^1.0.0" - } - }, - "error-ex": { - "version": "1.3.2", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.2.1", - "dev": true - } - } - }, - "es-abstract": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", - "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "regexp.prototype.flags": "^1.4.3", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es5-ext": { - "version": "0.10.53", - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-set": { - "version": "0.1.5", - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" - }, - "dependencies": { - "es6-symbol": { - "version": "3.1.1", - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - } - } - }, - "es6-symbol": { - "version": "3.1.3", - "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "esbuild": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.48.tgz", - "integrity": "sha512-w6N1Yn5MtqK2U1/WZTX9ZqUVb8IOLZkZ5AdHkT6x3cHDMVsYWC7WPdiLmx19w3i4Rwzy5LqsEMtVihG3e4rFzA==", - "dev": true, - "requires": { - "esbuild-android-64": "0.14.48", - "esbuild-android-arm64": "0.14.48", - "esbuild-darwin-64": "0.14.48", - "esbuild-darwin-arm64": "0.14.48", - "esbuild-freebsd-64": "0.14.48", - "esbuild-freebsd-arm64": "0.14.48", - "esbuild-linux-32": "0.14.48", - "esbuild-linux-64": "0.14.48", - "esbuild-linux-arm": "0.14.48", - "esbuild-linux-arm64": "0.14.48", - "esbuild-linux-mips64le": "0.14.48", - "esbuild-linux-ppc64le": "0.14.48", - "esbuild-linux-riscv64": "0.14.48", - "esbuild-linux-s390x": "0.14.48", - "esbuild-netbsd-64": "0.14.48", - "esbuild-openbsd-64": "0.14.48", - "esbuild-sunos-64": "0.14.48", - "esbuild-windows-32": "0.14.48", - "esbuild-windows-64": "0.14.48", - "esbuild-windows-arm64": "0.14.48" - } - }, - "esbuild-android-64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.48.tgz", - "integrity": "sha512-3aMjboap/kqwCUpGWIjsk20TtxVoKck8/4Tu19rubh7t5Ra0Yrpg30Mt1QXXlipOazrEceGeWurXKeFJgkPOUg==", - "dev": true, - "optional": true - }, - "esbuild-android-arm64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.48.tgz", - "integrity": "sha512-vptI3K0wGALiDq+EvRuZotZrJqkYkN5282iAfcffjI5lmGG9G1ta/CIVauhY42MBXwEgDJkweiDcDMRLzBZC4g==", - "dev": true, - "optional": true - }, - "esbuild-darwin-64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.48.tgz", - "integrity": "sha512-gGQZa4+hab2Va/Zww94YbshLuWteyKGD3+EsVon8EWTWhnHFRm5N9NbALNbwi/7hQ/hM1Zm4FuHg+k6BLsl5UA==", - "dev": true, - "optional": true - }, - "esbuild-darwin-arm64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.48.tgz", - "integrity": "sha512-bFjnNEXjhZT+IZ8RvRGNJthLWNHV5JkCtuOFOnjvo5pC0sk2/QVk0Qc06g2PV3J0TcU6kaPC3RN9yy9w2PSLEA==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.48.tgz", - "integrity": "sha512-1NOlwRxmOsnPcWOGTB10JKAkYSb2nue0oM1AfHWunW/mv3wERfJmnYlGzL3UAOIUXZqW8GeA2mv+QGwq7DToqA==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-arm64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.48.tgz", - "integrity": "sha512-gXqKdO8wabVcYtluAbikDH2jhXp+Klq5oCD5qbVyUG6tFiGhrC9oczKq3vIrrtwcxDQqK6+HDYK8Zrd4bCA9Gw==", - "dev": true, - "optional": true - }, - "esbuild-linux-32": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.48.tgz", - "integrity": "sha512-ghGyDfS289z/LReZQUuuKq9KlTiTspxL8SITBFQFAFRA/IkIvDpnZnCAKTCjGXAmUqroMQfKJXMxyjJA69c/nQ==", - "dev": true, - "optional": true - }, - "esbuild-linux-64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.48.tgz", - "integrity": "sha512-vni3p/gppLMVZLghI7oMqbOZdGmLbbKR23XFARKnszCIBpEMEDxOMNIKPmMItQrmH/iJrL1z8Jt2nynY0bE1ug==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.48.tgz", - "integrity": "sha512-+VfSV7Akh1XUiDNXgqgY1cUP1i2vjI+BmlyXRfVz5AfV3jbpde8JTs5Q9sYgaoq5cWfuKfoZB/QkGOI+QcL1Tw==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.48.tgz", - "integrity": "sha512-3CFsOlpoxlKPRevEHq8aAntgYGYkE1N9yRYAcPyng/p4Wyx0tPR5SBYsxLKcgPB9mR8chHEhtWYz6EZ+H199Zw==", - "dev": true, - "optional": true - }, - "esbuild-linux-mips64le": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.48.tgz", - "integrity": "sha512-cs0uOiRlPp6ymknDnjajCgvDMSsLw5mST2UXh+ZIrXTj2Ifyf2aAP3Iw4DiqgnyYLV2O/v/yWBJx+WfmKEpNLA==", - "dev": true, - "optional": true - }, - "esbuild-linux-ppc64le": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.48.tgz", - "integrity": "sha512-+2F0vJMkuI0Wie/wcSPDCqXvSFEELH7Jubxb7mpWrA/4NpT+/byjxDz0gG6R1WJoeDefcrMfpBx4GFNN1JQorQ==", - "dev": true, - "optional": true - }, - "esbuild-linux-riscv64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.48.tgz", - "integrity": "sha512-BmaK/GfEE+5F2/QDrIXteFGKnVHGxlnK9MjdVKMTfvtmudjY3k2t8NtlY4qemKSizc+QwyombGWTBDc76rxePA==", - "dev": true, - "optional": true - }, - "esbuild-linux-s390x": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.48.tgz", - "integrity": "sha512-tndw/0B9jiCL+KWKo0TSMaUm5UWBLsfCKVdbfMlb3d5LeV9WbijZ8Ordia8SAYv38VSJWOEt6eDCdOx8LqkC4g==", - "dev": true, - "optional": true - }, - "esbuild-netbsd-64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.48.tgz", - "integrity": "sha512-V9hgXfwf/T901Lr1wkOfoevtyNkrxmMcRHyticybBUHookznipMOHoF41Al68QBsqBxnITCEpjjd4yAos7z9Tw==", - "dev": true, - "optional": true - }, - "esbuild-openbsd-64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.48.tgz", - "integrity": "sha512-+IHf4JcbnnBl4T52egorXMatil/za0awqzg2Vy6FBgPcBpisDWT2sVz/tNdrK9kAqj+GZG/jZdrOkj7wsrNTKA==", - "dev": true, - "optional": true - }, - "esbuild-sunos-64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.48.tgz", - "integrity": "sha512-77m8bsr5wOpOWbGi9KSqDphcq6dFeJyun8TA+12JW/GAjyfTwVtOnN8DOt6DSPUfEV+ltVMNqtXUeTeMAxl5KA==", - "dev": true, - "optional": true - }, - "esbuild-windows-32": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.48.tgz", - "integrity": "sha512-EPgRuTPP8vK9maxpTGDe5lSoIBHGKO/AuxDncg5O3NkrPeLNdvvK8oywB0zGaAZXxYWfNNSHskvvDgmfVTguhg==", - "dev": true, - "optional": true - }, - "esbuild-windows-64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.48.tgz", - "integrity": "sha512-YmpXjdT1q0b8ictSdGwH3M8VCoqPpK1/UArze3X199w6u8hUx3V8BhAi1WjbsfDYRBanVVtduAhh2sirImtAvA==", - "dev": true, - "optional": true - }, - "esbuild-windows-arm64": { - "version": "0.14.48", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.48.tgz", - "integrity": "sha512-HHaOMCsCXp0rz5BT2crTka6MPWVno121NKApsGs/OIW5QC0ggC69YMGs1aJct9/9FSUF4A1xNE/cLvgB5svR4g==", - "dev": true, - "optional": true - }, - "escalade": { - "version": "3.1.1", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "dev": true - }, - "escodegen": { - "version": "1.2.0", - "requires": { - "esprima": "~1.0.4", - "estraverse": "~1.5.0", - "esutils": "~1.0.0", - "source-map": "~0.1.30" - }, - "dependencies": { - "esprima": { - "version": "1.0.4" - }, - "estraverse": { - "version": "1.5.1" - }, - "esutils": { - "version": "1.0.0" - }, - "source-map": { - "version": "0.1.43", - "optional": true, - "requires": { - "amdefine": ">=0.0.4" - } - } - } - }, - "eslint": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.25.0.tgz", - "integrity": "sha512-DVlJOZ4Pn50zcKW5bYH7GQK/9MsoQG2d5eDH0ebEkE8PbgzTTmtt/VTH9GGJ4BfeZCpBLqFfvsjX35UacUL83A==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.10.5", - "@humanwhocodes/module-importer": "^1.0.1", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "globby": "^11.1.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "escape-string-regexp": { - "version": "4.0.0", - "dev": true - }, - "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - } - } - }, - "eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", - "dev": true, - "requires": {} - }, - "eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - }, - "espree": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", - "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", - "dev": true, - "requires": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "dependencies": { - "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1" - }, - "esquery": { - "version": "1.4.0", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0" - }, - "estree-is-function": { - "version": "1.0.0" - }, - "estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true - }, - "esutils": { - "version": "2.0.3" - }, - "event-emitter": { - "version": "0.3.5", - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "execa": { - "version": "5.1.1", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "dependencies": { - "get-stream": { - "version": "6.0.1", - "dev": true - }, - "human-signals": { - "version": "2.1.0", - "dev": true - } - } - }, - "exit-on-epipe": { - "version": "1.0.1" - }, - "expand-tilde": { - "version": "2.0.2", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, - "ext": { - "version": "1.5.0", - "requires": { - "type": "^2.5.0" - }, - "dependencies": { - "type": { - "version": "2.5.0" - } - } - }, - "external-editor": { - "version": "3.1.0", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "dev": true - }, - "fast-diff": { - "version": "1.2.0", - "dev": true - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6" - }, - "fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true - }, - "fastq": { - "version": "1.8.0", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fflate": { - "version": "0.3.11" - }, - "figures": { - "version": "2.0.0", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "requires": { - "minimatch": "^5.0.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "fill-range": { - "version": "7.0.1", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-root": { - "version": "1.1.0", - "dev": true - }, - "find-up": { - "version": "4.1.0", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "find-versions": { - "version": "4.0.0", - "dev": true, - "requires": { - "semver-regex": "^3.1.2" - } - }, - "flat-cache": { - "version": "3.0.4", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatpickr": { - "version": "4.6.13", - "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz", - "integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==" - }, - "flatted": { - "version": "3.1.1", - "dev": true - }, - "fontkit": { - "version": "1.8.1", - "requires": { - "babel-runtime": "^6.26.0", - "brfs": "^2.0.0", - "brotli": "^1.2.0", - "browserify-optional": "^1.0.1", - "clone": "^1.0.4", - "deep-equal": "^1.0.0", - "dfa": "^1.2.0", - "restructure": "^0.5.3", - "tiny-inflate": "^1.0.2", - "unicode-properties": "^1.2.2", - "unicode-trie": "^0.3.0" - }, - "dependencies": { - "unicode-trie": { - "version": "0.3.1", - "requires": { - "pako": "^0.2.5", - "tiny-inflate": "^1.0.0" - } - } - } - }, - "form-data": { - "version": "3.0.0", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "frac": { - "version": "1.1.2" - }, - "fraction.js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", - "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", - "dev": true - }, - "from2": { - "version": "2.3.0", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "fromentries": { - "version": "1.3.2", - "dev": true - }, - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "dependencies": { - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } - } - }, - "fs.realpath": { - "version": "1.0.0", - "dev": true - }, - "fscreen": { - "version": "1.2.0" - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1" - }, - "function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - } - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" - }, - "fuse.js": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.5.3.tgz", - "integrity": "sha512-sA5etGE7yD/pOqivZRBvUBd/NaL2sjAu6QuSaFoe1H2BrJSkH/T/UXAJ8CdXdw7DvY3Hs8CXKYkDWX7RiP5KOg==" - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-assigned-identifiers": { - "version": "1.2.0" - }, - "get-caller-file": { - "version": "2.0.5", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "dev": true - }, - "get-stream": { - "version": "5.2.0", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "git-log-parser": { - "version": "1.2.0", - "dev": true, - "requires": { - "argv-formatter": "~1.0.0", - "spawn-error-forwarder": "~1.0.0", - "split2": "~1.0.0", - "stream-combiner2": "~1.1.1", - "through2": "~2.0.0", - "traverse": "~0.6.6" - }, - "dependencies": { - "split2": { - "version": "1.0.0", - "dev": true, - "requires": { - "through2": "~2.0.0" - } - }, - "through2": { - "version": "2.0.5", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, - "git-raw-commits": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", - "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", - "dev": true, - "requires": { - "dargs": "^7.0.0", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "global-dirs": { - "version": "0.1.1", - "dev": true, - "requires": { - "ini": "^1.3.4" - } - }, - "global-modules": { - "version": "1.0.0", - "dev": true, - "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - } - }, - "global-prefix": { - "version": "1.0.2", - "dev": true, - "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - } - }, - "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - }, - "dependencies": { - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "globjoin": { - "version": "0.1.4", - "dev": true - }, - "got": { - "version": "11.8.5", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", - "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", - "dev": true, - "requires": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - } - }, - "graceful-fs": { - "version": "4.2.4", - "dev": true - }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "handlebars": { - "version": "4.7.7", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "dev": true - } - } - }, - "hard-rejection": { - "version": "2.1.0", - "dev": true - }, - "has": { - "version": "1.0.3", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "dev": true - }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "has-tostringtag": { - "version": "1.0.0", - "requires": { - "has-symbols": "^1.0.2" - } - }, - "homedir-polyfill": { - "version": "1.0.3", - "dev": true, - "requires": { - "parse-passwd": "^1.0.0" - } - }, - "hook-std": { - "version": "2.0.0", - "dev": true - }, - "hosted-git-info": { - "version": "4.0.2", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "hpagent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.0.0.tgz", - "integrity": "sha512-SCleE2Uc1bM752ymxg8QXYGW0TWtAV4ZW3TqH1aOnyi6T6YW2xadCcclm5qeVjvMvfQ2RKNtZxO7uVb9CTPt1A==", - "dev": true - }, - "html-encoding-sniffer": { - "version": "2.0.1", - "requires": { - "whatwg-encoding": "^1.0.5" - } - }, - "html-tags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", - "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", - "dev": true - }, - "http-cache-semantics": { - "version": "4.1.0", - "dev": true - }, - "http-proxy-agent": { - "version": "4.0.1", - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "http2-wrapper": { - "version": "1.0.3", - "dev": true, - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "dependencies": { - "quick-lru": { - "version": "5.1.1", - "dev": true - } - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "human-signals": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", - "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", - "dev": true - }, - "husky": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz", - "integrity": "sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "idb": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.2.tgz", - "integrity": "sha512-jjKrT1EnyZewQ/gCBb/eyiYrhGzws2FeY92Yx8qT9S9GeQAmo4JFVIiWRIfKW/6Ob9A+UDAOW9j9jn58fy2HIg==", - "dev": true - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "dev": true - } - } - }, - "import-lazy": { - "version": "4.0.0", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4" - }, - "ini": { - "version": "1.3.8", - "dev": true - }, - "inquirer": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", - "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "rxjs": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", - "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - }, - "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true - } - } - }, - "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==" - }, - "into-stream": { - "version": "6.0.0", - "dev": true, - "requires": { - "from2": "^2.3.0", - "p-is-promise": "^3.0.0" - } - }, - "is-arguments": { - "version": "1.1.0", - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true - }, - "is-ci": { - "version": "3.0.1", - "dev": true, - "requires": { - "ci-info": "^3.2.0" - } - }, - "is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.2" - }, - "is-extglob": { - "version": "2.1.1", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true - }, - "is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "dev": true - }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-obj": { - "version": "2.0.0", - "dev": true - }, - "is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-plain-obj": { - "version": "1.1.0", - "dev": true - }, - "is-plain-object": { - "version": "5.0.0", - "dev": true - }, - "is-potential-custom-element-name": { - "version": "1.0.1" - }, - "is-regex": { - "version": "1.1.4", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", - "dev": true - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-stream": { - "version": "2.0.0", - "dev": true - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-text-path": { - "version": "1.0.1", - "dev": true, - "requires": { - "text-extensions": "^1.0.0" - } - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true - }, - "is-utf8": { - "version": "0.2.1", - "dev": true - }, - "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-windows": { - "version": "1.0.2", - "dev": true - }, - "isarray": { - "version": "1.0.0" - }, - "isexe": { - "version": "2.0.0", - "dev": true - }, - "issue-parser": { - "version": "6.0.0", - "dev": true, - "requires": { - "lodash.capitalize": "^4.2.1", - "lodash.escaperegexp": "^4.1.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.uniqby": "^4.7.0" - } - }, - "jake": { - "version": "10.8.5", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", - "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", - "dev": true, - "requires": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" - } - }, - "java-properties": { - "version": "1.0.2", - "dev": true - }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-sdsl": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", - "integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "jsdom": { - "version": "16.7.0", - "requires": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "acorn": { - "version": "8.5.0" - }, - "escodegen": { - "version": "2.0.0", - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, - "estraverse": { - "version": "5.2.0" - }, - "levn": { - "version": "0.3.0", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2" - }, - "source-map": { - "version": "0.6.1", - "optional": true - }, - "type-check": { - "version": "0.3.2", - "requires": { - "prelude-ls": "~1.1.2" - } - } - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-buffer": { - "version": "3.0.1", - "dev": true - }, - "json-fixer": { - "version": "1.6.13", - "resolved": "https://registry.npmjs.org/json-fixer/-/json-fixer-1.6.13.tgz", - "integrity": "sha512-DKQ71M+0uwAG3QsUkeVgh6XREw/OkpnTfHfM+sdmxRjHvYZ8PlcMVF4ibsHQ1ckR63NROs68qUr1I0u6yPVePQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.6", - "chalk": "^4.1.2", - "pegjs": "^0.10.0" - } - }, - "json-parse-better-errors": { - "version": "1.0.2", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.0", - "dev": true - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "dev": true - }, - "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "dev": true - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - }, - "dependencies": { - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } - } - }, - "jsonparse": { - "version": "1.3.1", - "dev": true - }, - "jsonpointer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz", - "integrity": "sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg==", - "dev": true - }, - "JSONStream": { - "version": "1.3.5", - "dev": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "keyv": { - "version": "4.0.4", - "dev": true, - "requires": { - "json-buffer": "3.0.1" - } - }, - "kind-of": { - "version": "6.0.3", - "dev": true - }, - "known-css-properties": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.25.0.tgz", - "integrity": "sha512-b0/9J1O9Jcyik1GC6KC42hJ41jKwdO/Mq8Mdo5sYN+IuRTXs2YFHZC3kZSx6ueusqa95x3wLYe/ytKjbAfGixA==", - "dev": true - }, - "leaflet": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.2.tgz", - "integrity": "sha512-Kc77HQvWO+y9y2oIs3dn5h5sy2kr3j41ewdqCMEUA4N89lgfUUfOBy7wnnHEstDpefiGFObq12FdopGRMx4J7g==" - }, - "leaflet.markercluster": { - "version": "1.5.3", - "requires": {} - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.4.1", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lilconfig": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.5.tgz", - "integrity": "sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==", - "dev": true - }, - "linebreak": { - "version": "1.0.2", - "requires": { - "base64-js": "0.0.8", - "brfs": "^2.0.2", - "unicode-trie": "^1.0.0" - }, - "dependencies": { - "base64-js": { - "version": "0.0.8" - }, - "unicode-trie": { - "version": "1.0.0", - "requires": { - "pako": "^0.2.5", - "tiny-inflate": "^1.0.0" - } - } - } - }, - "lines-and-columns": { - "version": "1.1.6", - "dev": true - }, - "lint-staged": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.0.3.tgz", - "integrity": "sha512-9hmrwSCFroTSYLjflGI8Uk+GWAwMB4OlpU4bMJEAT5d/llQwtYKoim4bLOyLCuWFAhWEupE0vkIFqtw/WIsPug==", - "dev": true, - "requires": { - "cli-truncate": "^3.1.0", - "colorette": "^2.0.17", - "commander": "^9.3.0", - "debug": "^4.3.4", - "execa": "^6.1.0", - "lilconfig": "2.0.5", - "listr2": "^4.0.5", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-inspect": "^1.12.2", - "pidtree": "^0.6.0", - "string-argv": "^0.3.1", - "yaml": "^2.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true - }, - "ansi-styles": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz", - "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==", - "dev": true - }, - "cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", - "dev": true, - "requires": { - "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" - } - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "execa": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", - "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^3.0.1", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - } - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "dev": true - }, - "is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true - }, - "mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true - }, - "npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", - "dev": true, - "requires": { - "path-key": "^4.0.0" - } - }, - "onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "requires": { - "mimic-fn": "^4.0.0" - } - }, - "path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true - }, - "slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dev": true, - "requires": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - } - }, - "string-width": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.0.1.tgz", - "integrity": "sha512-5ohWO/M4//8lErlUUtrFy3b11GtNOuMOU0ysKCDXFcfXuuvUXu95akgj/i8ofmaGdN0hCqyl6uu9i8dS/mQp5g==", - "dev": true, - "requires": { - "emoji-regex": "^9.2.2", - "is-fullwidth-code-point": "^4.0.0", - "strip-ansi": "^7.0.1" - } - }, - "strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dev": true, - "requires": { - "ansi-regex": "^6.0.1" - } - }, - "strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true - }, - "yaml": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz", - "integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==", - "dev": true - } - } - }, - "listr2": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.5.tgz", - "integrity": "sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==", - "dev": true, - "requires": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.16", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.5.5", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "rxjs": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", - "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - }, - "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true - } - } - }, - "lit": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/lit/-/lit-2.4.0.tgz", - "integrity": "sha512-fdgzxEtLrZFQU/BqTtxFQCLwlZd9bdat+ltzSFjvWkZrs7eBmeX0L5MHUMb3kYIkuS8Xlfnii/iI5klirF8/Xg==", - "requires": { - "@lit/reactive-element": "^1.4.0", - "lit-element": "^3.2.0", - "lit-html": "^2.4.0" - } - }, - "lit-element": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.2.0.tgz", - "integrity": "sha512-HbE7yt2SnUtg5DCrWt028oaU4D5F4k/1cntAFHTkzY8ZIa8N0Wmu92PxSxucsQSOXlODFrICkQ5x/tEshKi13g==", - "requires": { - "@lit/reactive-element": "^1.3.0", - "lit-html": "^2.2.0" - } - }, - "lit-html": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.4.0.tgz", - "integrity": "sha512-G6qXu4JNUpY6aaF2VMfaszhO9hlWw0hOTRFDmuMheg/nDYGB+2RztUSOyrzALAbr8Nh0Y7qjhYkReh3rPnplVg==", - "requires": { - "@types/trusted-types": "^2.0.2" - } - }, - "load-json-file": { - "version": "4.0.0", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "dev": true - }, - "strip-bom": { - "version": "3.0.0", - "dev": true - } - } - }, - "locate-path": { - "version": "5.0.0", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21" - }, - "lodash.capitalize": { - "version": "4.2.1", - "dev": true - }, - "lodash.castarray": { - "version": "4.4.0", - "dev": true - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "lodash.escaperegexp": { - "version": "4.1.2", - "dev": true - }, - "lodash.ismatch": { - "version": "4.4.0", - "dev": true - }, - "lodash.isplainobject": { - "version": "4.0.6", - "dev": true - }, - "lodash.isstring": { - "version": "4.0.1", - "dev": true - }, - "lodash.map": { - "version": "4.6.0", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "dev": true - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "dev": true - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "dev": true - }, - "lodash.uniqby": { - "version": "4.7.0", - "dev": true - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } - }, - "log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - } - } - }, - "longest": { - "version": "2.0.1", - "dev": true - }, - "lowercase-keys": { - "version": "2.0.0", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "magic-string": { - "version": "0.25.1", - "requires": { - "sourcemap-codec": "^1.4.1" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "map-obj": { - "version": "4.2.0", - "dev": true - }, - "marked": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.1.tgz", - "integrity": "sha512-0cNMnTcUJPxbA6uWmCmjWz4NJRe/0Xfk2NhXCUHjew9qJzFN20krFnsUe7QynwqOwa5m1fZ4UDg0ycKFVC0ccw==" - }, - "marked-terminal": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.0.0.tgz", - "integrity": "sha512-26604GmGmW63ElxcXpE2xfMdbtgD/qiwIqOh/+5+uPe6NVU4bU433+wvPTfq6NZcGr16KWqwu/dzsKxg3IL2Xw==", - "dev": true, - "requires": { - "ansi-escapes": "^5.0.0", - "cardinal": "^2.1.1", - "chalk": "^5.0.0", - "cli-table3": "^0.6.0", - "node-emoji": "^1.11.0", - "supports-hyperlinks": "^2.2.0" - }, - "dependencies": { - "ansi-escapes": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", - "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", - "dev": true, - "requires": { - "type-fest": "^1.0.2" - } - }, - "chalk": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.0.tgz", - "integrity": "sha512-/duVOqst+luxCQRKEo4bNxinsOQtMP80ZYm7mMqzuh5PociNL0PvmHFvREJ9ueYL2TxlHjBcmLCdmocx9Vg+IQ==", - "dev": true - }, - "type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true - } - } - }, - "mathml-tag-names": { - "version": "2.1.3", - "dev": true - }, - "mdn-data": { - "version": "2.0.14", - "dev": true - }, - "meow": { - "version": "8.1.2", - "dev": true, - "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - } - }, - "merge-source-map": { - "version": "1.0.4", - "requires": { - "source-map": "^0.5.6" - } - }, - "merge-stream": { - "version": "2.0.0", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime": { - "version": "3.0.0", - "dev": true - }, - "mime-db": { - "version": "1.45.0" - }, - "mime-types": { - "version": "2.1.28", - "requires": { - "mime-db": "1.45.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "dev": true - }, - "mimic-response": { - "version": "1.0.1", - "dev": true - }, - "min-indent": { - "version": "1.0.1", - "dev": true - }, - "mini-svg-data-uri": { - "version": "1.2.3", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "minimist-options": { - "version": "4.1.0", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - } - }, - "mitt": { - "version": "3.0.0" - }, - "modify-values": { - "version": "1.0.1", - "dev": true - }, - "ms": { - "version": "2.1.2" - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" - }, - "natural-compare": { - "version": "1.4.0", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "dev": true - }, - "nerf-dart": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", - "integrity": "sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo=", - "dev": true - }, - "next-tick": { - "version": "1.0.0" - }, - "node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "dev": true, - "requires": { - "lodash": "^4.17.21" - } - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - }, - "dependencies": { - "tr46": { - "version": "0.0.3", - "dev": true - }, - "webidl-conversions": { - "version": "3.0.1", - "dev": true - }, - "whatwg-url": { - "version": "5.0.0", - "dev": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } - }, - "node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "dev": true - }, - "normalize-package-data": { - "version": "3.0.2", - "dev": true, - "requires": { - "hosted-git-info": "^4.0.1", - "resolve": "^1.20.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "normalize-path": { - "version": "3.0.0", - "dev": true - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true - }, - "normalize-url": { - "version": "6.1.0", - "dev": true - }, - "npm": { - "version": "8.13.2", - "resolved": "https://registry.npmjs.org/npm/-/npm-8.13.2.tgz", - "integrity": "sha512-aS6q/QKxkw9mTX8gR7Ft38BcRkW1i+h3sI1yAFmfQ30Yl1a1G4ZX3oNGDzaLCilU5ThFZQBS1F4ZSZsrVxJ7HA==", - "dev": true, - "requires": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^5.0.4", - "@npmcli/ci-detect": "^2.0.0", - "@npmcli/config": "^4.1.0", - "@npmcli/fs": "^2.1.0", - "@npmcli/map-workspaces": "^2.0.3", - "@npmcli/package-json": "^2.0.0", - "@npmcli/run-script": "^4.1.5", - "abbrev": "~1.1.1", - "archy": "~1.0.0", - "cacache": "^16.1.1", - "chalk": "^4.1.2", - "chownr": "^2.0.0", - "cli-columns": "^4.0.0", - "cli-table3": "^0.6.2", - "columnify": "^1.6.0", - "fastest-levenshtein": "^1.0.12", - "glob": "^8.0.1", - "graceful-fs": "^4.2.10", - "hosted-git-info": "^5.0.0", - "ini": "^3.0.0", - "init-package-json": "^3.0.2", - "is-cidr": "^4.0.2", - "json-parse-even-better-errors": "^2.3.1", - "libnpmaccess": "^6.0.2", - "libnpmdiff": "^4.0.2", - "libnpmexec": "^4.0.2", - "libnpmfund": "^3.0.1", - "libnpmhook": "^8.0.2", - "libnpmorg": "^4.0.2", - "libnpmpack": "^4.0.2", - "libnpmpublish": "^6.0.2", - "libnpmsearch": "^5.0.2", - "libnpmteam": "^4.0.2", - "libnpmversion": "^3.0.1", - "make-fetch-happen": "^10.1.8", - "minipass": "^3.1.6", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "mkdirp-infer-owner": "^2.0.0", - "ms": "^2.1.2", - "node-gyp": "^9.0.0", - "nopt": "^5.0.0", - "npm-audit-report": "^3.0.0", - "npm-install-checks": "^5.0.0", - "npm-package-arg": "^9.0.2", - "npm-pick-manifest": "^7.0.1", - "npm-profile": "^6.1.0", - "npm-registry-fetch": "^13.1.1", - "npm-user-validate": "^1.0.1", - "npmlog": "^6.0.2", - "opener": "^1.5.2", - "pacote": "^13.6.1", - "parse-conflict-json": "^2.0.2", - "proc-log": "^2.0.1", - "qrcode-terminal": "^0.12.0", - "read": "~1.0.7", - "read-package-json": "^5.0.1", - "read-package-json-fast": "^2.0.3", - "readdir-scoped-modules": "^1.1.0", - "rimraf": "^3.0.2", - "semver": "^7.3.7", - "ssri": "^9.0.1", - "tar": "^6.1.11", - "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", - "treeverse": "^2.0.0", - "validate-npm-package-name": "^4.0.0", - "which": "^2.0.2", - "write-file-atomic": "^4.0.1" - }, - "dependencies": { - "@colors/colors": { - "version": "1.5.0", - "bundled": true, - "dev": true, - "optional": true - }, - "@gar/promisify": { - "version": "1.1.3", - "bundled": true, - "dev": true - }, - "@isaacs/string-locale-compare": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "@npmcli/arborist": { - "version": "5.2.3", - "bundled": true, - "dev": true, - "requires": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/installed-package-contents": "^1.0.7", - "@npmcli/map-workspaces": "^2.0.3", - "@npmcli/metavuln-calculator": "^3.0.1", - "@npmcli/move-file": "^2.0.0", - "@npmcli/name-from-folder": "^1.0.1", - "@npmcli/node-gyp": "^2.0.0", - "@npmcli/package-json": "^2.0.0", - "@npmcli/run-script": "^4.1.3", - "bin-links": "^3.0.0", - "cacache": "^16.0.6", - "common-ancestor-path": "^1.0.1", - "json-parse-even-better-errors": "^2.3.1", - "json-stringify-nice": "^1.1.4", - "mkdirp": "^1.0.4", - "mkdirp-infer-owner": "^2.0.0", - "nopt": "^5.0.0", - "npm-install-checks": "^5.0.0", - "npm-package-arg": "^9.0.0", - "npm-pick-manifest": "^7.0.0", - "npm-registry-fetch": "^13.0.0", - "npmlog": "^6.0.2", - "pacote": "^13.6.1", - "parse-conflict-json": "^2.0.1", - "proc-log": "^2.0.0", - "promise-all-reject-late": "^1.0.0", - "promise-call-limit": "^1.0.1", - "read-package-json-fast": "^2.0.2", - "readdir-scoped-modules": "^1.1.0", - "rimraf": "^3.0.2", - "semver": "^7.3.7", - "ssri": "^9.0.0", - "treeverse": "^2.0.0", - "walk-up-path": "^1.0.0" - } - }, - "@npmcli/ci-detect": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "@npmcli/config": { - "version": "4.1.0", - "bundled": true, - "dev": true, - "requires": { - "@npmcli/map-workspaces": "^2.0.2", - "ini": "^3.0.0", - "mkdirp-infer-owner": "^2.0.0", - "nopt": "^5.0.0", - "proc-log": "^2.0.0", - "read-package-json-fast": "^2.0.3", - "semver": "^7.3.5", - "walk-up-path": "^1.0.0" - } - }, - "@npmcli/disparity-colors": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "requires": { - "ansi-styles": "^4.3.0" - } - }, - "@npmcli/fs": { - "version": "2.1.0", - "bundled": true, - "dev": true, - "requires": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" - } - }, - "@npmcli/git": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "@npmcli/promise-spawn": "^3.0.0", - "lru-cache": "^7.4.4", - "mkdirp": "^1.0.4", - "npm-pick-manifest": "^7.0.0", - "proc-log": "^2.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^2.0.2" - } - }, - "@npmcli/installed-package-contents": { - "version": "1.0.7", - "bundled": true, - "dev": true, - "requires": { - "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "@npmcli/map-workspaces": { - "version": "2.0.3", - "bundled": true, - "dev": true, - "requires": { - "@npmcli/name-from-folder": "^1.0.1", - "glob": "^8.0.1", - "minimatch": "^5.0.1", - "read-package-json-fast": "^2.0.3" - } - }, - "@npmcli/metavuln-calculator": { - "version": "3.1.1", - "bundled": true, - "dev": true, - "requires": { - "cacache": "^16.0.0", - "json-parse-even-better-errors": "^2.3.1", - "pacote": "^13.0.3", - "semver": "^7.3.5" - } - }, - "@npmcli/move-file": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "requires": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - } - }, - "@npmcli/name-from-folder": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "@npmcli/node-gyp": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "@npmcli/package-json": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "requires": { - "json-parse-even-better-errors": "^2.3.1" - } - }, - "@npmcli/promise-spawn": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "infer-owner": "^1.0.4" - } - }, - "@npmcli/run-script": { - "version": "4.1.5", - "bundled": true, - "dev": true, - "requires": { - "@npmcli/node-gyp": "^2.0.0", - "@npmcli/promise-spawn": "^3.0.0", - "node-gyp": "^9.0.0", - "read-package-json-fast": "^2.0.3", - "which": "^2.0.2" - } - }, - "@tootallnate/once": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true - }, - "agent-base": { - "version": "6.0.2", - "bundled": true, - "dev": true, - "requires": { - "debug": "4" - } - }, - "agentkeepalive": { - "version": "4.2.1", - "bundled": true, - "dev": true, - "requires": { - "debug": "^4.1.0", - "depd": "^1.1.2", - "humanize-ms": "^1.2.1" - } - }, - "aggregate-error": { - "version": "3.1.0", - "bundled": true, - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ansi-regex": { - "version": "5.0.1", - "bundled": true, - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "bundled": true, - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "aproba": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "archy": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "are-we-there-yet": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - } - }, - "asap": { - "version": "2.0.6", - "bundled": true, - "dev": true - }, - "balanced-match": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "bin-links": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "cmd-shim": "^5.0.0", - "mkdirp-infer-owner": "^2.0.0", - "npm-normalize-package-bin": "^1.0.0", - "read-cmd-shim": "^3.0.0", - "rimraf": "^3.0.0", - "write-file-atomic": "^4.0.0" - } - }, - "binary-extensions": { - "version": "2.2.0", - "bundled": true, - "dev": true - }, - "brace-expansion": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "builtins": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "requires": { - "semver": "^7.0.0" - } - }, - "cacache": { - "version": "16.1.1", - "bundled": true, - "dev": true, - "requires": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^1.1.1" - } - }, - "chalk": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "chownr": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "cidr-regex": { - "version": "3.1.1", - "bundled": true, - "dev": true, - "requires": { - "ip-regex": "^4.1.0" - } - }, - "clean-stack": { - "version": "2.2.0", - "bundled": true, - "dev": true - }, - "cli-columns": { - "version": "4.0.0", - "bundled": true, - "dev": true, - "requires": { - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - } - }, - "cli-table3": { - "version": "0.6.2", - "bundled": true, - "dev": true, - "requires": { - "@colors/colors": "1.5.0", - "string-width": "^4.2.0" - } - }, - "clone": { - "version": "1.0.4", - "bundled": true, - "dev": true - }, - "cmd-shim": { - "version": "5.0.0", - "bundled": true, - "dev": true, - "requires": { - "mkdirp-infer-owner": "^2.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "bundled": true, - "dev": true - }, - "color-support": { - "version": "1.1.3", - "bundled": true, - "dev": true - }, - "columnify": { - "version": "1.6.0", - "bundled": true, - "dev": true, - "requires": { - "strip-ansi": "^6.0.1", - "wcwidth": "^1.0.0" - } - }, - "common-ancestor-path": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "debug": { - "version": "4.3.4", - "bundled": true, - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "bundled": true, - "dev": true - } - } - }, - "debuglog": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "defaults": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "requires": { - "clone": "^1.0.2" - } - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "depd": { - "version": "1.1.2", - "bundled": true, - "dev": true - }, - "dezalgo": { - "version": "1.0.4", - "bundled": true, - "dev": true, - "requires": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "diff": { - "version": "5.0.0", - "bundled": true, - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "bundled": true, - "dev": true - }, - "encoding": { - "version": "0.1.13", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "iconv-lite": "^0.6.2" - } - }, - "env-paths": { - "version": "2.2.1", - "bundled": true, - "dev": true - }, - "err-code": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "fastest-levenshtein": { - "version": "1.0.12", - "bundled": true, - "dev": true - }, - "fs-minipass": { - "version": "2.1.0", - "bundled": true, - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "bundled": true, - "dev": true - }, - "gauge": { - "version": "4.0.4", - "bundled": true, - "dev": true, - "requires": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - } - }, - "glob": { - "version": "8.0.3", - "bundled": true, - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "bundled": true, - "dev": true - }, - "has": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "bundled": true, - "dev": true - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true - }, - "hosted-git-info": { - "version": "5.0.0", - "bundled": true, - "dev": true, - "requires": { - "lru-cache": "^7.5.1" - } - }, - "http-cache-semantics": { - "version": "4.1.0", - "bundled": true, - "dev": true - }, - "http-proxy-agent": { - "version": "5.0.0", - "bundled": true, - "dev": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "humanize-ms": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "requires": { - "ms": "^2.0.0" - } - }, - "iconv-lite": { - "version": "0.6.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - }, - "ignore-walk": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "requires": { - "minimatch": "^5.0.1" - } - }, - "imurmurhash": { - "version": "0.1.4", - "bundled": true, - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "bundled": true, - "dev": true - }, - "infer-owner": { - "version": "1.0.4", - "bundled": true, - "dev": true - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true, - "dev": true - }, - "ini": { - "version": "3.0.0", - "bundled": true, - "dev": true - }, - "init-package-json": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "requires": { - "npm-package-arg": "^9.0.1", - "promzard": "^0.3.0", - "read": "^1.0.7", - "read-package-json": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "^4.0.0" - } - }, - "ip": { - "version": "1.1.8", - "bundled": true, - "dev": true - }, - "ip-regex": { - "version": "4.3.0", - "bundled": true, - "dev": true - }, - "is-cidr": { - "version": "4.0.2", - "bundled": true, - "dev": true, - "requires": { - "cidr-regex": "^3.1.1" - } - }, - "is-core-module": { - "version": "2.9.0", - "bundled": true, - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "bundled": true, - "dev": true - }, - "is-lambda": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "isexe": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "bundled": true, - "dev": true - }, - "json-stringify-nice": { - "version": "1.1.4", - "bundled": true, - "dev": true - }, - "jsonparse": { - "version": "1.3.1", - "bundled": true, - "dev": true - }, - "just-diff": { - "version": "5.0.2", - "bundled": true, - "dev": true - }, - "just-diff-apply": { - "version": "5.2.0", - "bundled": true, - "dev": true - }, - "libnpmaccess": { - "version": "6.0.3", - "bundled": true, - "dev": true, - "requires": { - "aproba": "^2.0.0", - "minipass": "^3.1.1", - "npm-package-arg": "^9.0.1", - "npm-registry-fetch": "^13.0.0" - } - }, - "libnpmdiff": { - "version": "4.0.4", - "bundled": true, - "dev": true, - "requires": { - "@npmcli/disparity-colors": "^2.0.0", - "@npmcli/installed-package-contents": "^1.0.7", - "binary-extensions": "^2.2.0", - "diff": "^5.0.0", - "minimatch": "^5.0.1", - "npm-package-arg": "^9.0.1", - "pacote": "^13.6.1", - "tar": "^6.1.0" - } - }, - "libnpmexec": { - "version": "4.0.8", - "bundled": true, - "dev": true, - "requires": { - "@npmcli/arborist": "^5.0.0", - "@npmcli/ci-detect": "^2.0.0", - "@npmcli/run-script": "^4.1.3", - "chalk": "^4.1.0", - "mkdirp-infer-owner": "^2.0.0", - "npm-package-arg": "^9.0.1", - "npmlog": "^6.0.2", - "pacote": "^13.6.1", - "proc-log": "^2.0.0", - "read": "^1.0.7", - "read-package-json-fast": "^2.0.2", - "walk-up-path": "^1.0.0" - } - }, - "libnpmfund": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "requires": { - "@npmcli/arborist": "^5.0.0" - } - }, - "libnpmhook": { - "version": "8.0.3", - "bundled": true, - "dev": true, - "requires": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^13.0.0" - } - }, - "libnpmorg": { - "version": "4.0.3", - "bundled": true, - "dev": true, - "requires": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^13.0.0" - } - }, - "libnpmpack": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "requires": { - "@npmcli/run-script": "^4.1.3", - "npm-package-arg": "^9.0.1", - "pacote": "^13.6.1" - } - }, - "libnpmpublish": { - "version": "6.0.4", - "bundled": true, - "dev": true, - "requires": { - "normalize-package-data": "^4.0.0", - "npm-package-arg": "^9.0.1", - "npm-registry-fetch": "^13.0.0", - "semver": "^7.3.7", - "ssri": "^9.0.0" - } - }, - "libnpmsearch": { - "version": "5.0.3", - "bundled": true, - "dev": true, - "requires": { - "npm-registry-fetch": "^13.0.0" - } - }, - "libnpmteam": { - "version": "4.0.3", - "bundled": true, - "dev": true, - "requires": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^13.0.0" - } - }, - "libnpmversion": { - "version": "3.0.6", - "bundled": true, - "dev": true, - "requires": { - "@npmcli/git": "^3.0.0", - "@npmcli/run-script": "^4.1.3", - "json-parse-even-better-errors": "^2.3.1", - "proc-log": "^2.0.0", - "semver": "^7.3.7" - } - }, - "lru-cache": { - "version": "7.9.0", - "bundled": true, - "dev": true - }, - "make-fetch-happen": { - "version": "10.1.8", - "bundled": true, - "dev": true, - "requires": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" - } - }, - "minimatch": { - "version": "5.1.0", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "minipass": { - "version": "3.1.6", - "bundled": true, - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "minipass-collect": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-fetch": { - "version": "2.1.0", - "bundled": true, - "dev": true, - "requires": { - "encoding": "^0.1.13", - "minipass": "^3.1.6", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - } - }, - "minipass-flush": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-json-stream": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, - "minipass-pipeline": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-sized": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minizlib": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - } - }, - "mkdirp": { - "version": "1.0.4", - "bundled": true, - "dev": true - }, - "mkdirp-infer-owner": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "requires": { - "chownr": "^2.0.0", - "infer-owner": "^1.0.4", - "mkdirp": "^1.0.3" - } - }, - "ms": { - "version": "2.1.3", - "bundled": true, - "dev": true - }, - "mute-stream": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, - "negotiator": { - "version": "0.6.3", - "bundled": true, - "dev": true - }, - "node-gyp": { - "version": "9.0.0", - "bundled": true, - "dev": true, - "requires": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^10.0.3", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "dependencies": { - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "glob": { - "version": "7.2.3", - "bundled": true, - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.1.2", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "nopt": { - "version": "5.0.0", - "bundled": true, - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "normalize-package-data": { - "version": "4.0.0", - "bundled": true, - "dev": true, - "requires": { - "hosted-git-info": "^5.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - } - }, - "npm-audit-report": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "chalk": "^4.0.0" - } - }, - "npm-bundled": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-install-checks": { - "version": "5.0.0", - "bundled": true, - "dev": true, - "requires": { - "semver": "^7.1.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "npm-package-arg": { - "version": "9.0.2", - "bundled": true, - "dev": true, - "requires": { - "hosted-git-info": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^4.0.0" - } - }, - "npm-packlist": { - "version": "5.1.1", - "bundled": true, - "dev": true, - "requires": { - "glob": "^8.0.1", - "ignore-walk": "^5.0.1", - "npm-bundled": "^1.1.2", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-pick-manifest": { - "version": "7.0.1", - "bundled": true, - "dev": true, - "requires": { - "npm-install-checks": "^5.0.0", - "npm-normalize-package-bin": "^1.0.1", - "npm-package-arg": "^9.0.0", - "semver": "^7.3.5" - } - }, - "npm-profile": { - "version": "6.1.0", - "bundled": true, - "dev": true, - "requires": { - "npm-registry-fetch": "^13.0.1", - "proc-log": "^2.0.0" - } - }, - "npm-registry-fetch": { - "version": "13.1.1", - "bundled": true, - "dev": true, - "requires": { - "make-fetch-happen": "^10.0.6", - "minipass": "^3.1.6", - "minipass-fetch": "^2.0.3", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.1.2", - "npm-package-arg": "^9.0.1", - "proc-log": "^2.0.0" - } - }, - "npm-user-validate": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "npmlog": { - "version": "6.0.2", - "bundled": true, - "dev": true, - "requires": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - } - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "opener": { - "version": "1.5.2", - "bundled": true, - "dev": true - }, - "p-map": { - "version": "4.0.0", - "bundled": true, - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "pacote": { - "version": "13.6.1", - "bundled": true, - "dev": true, - "requires": { - "@npmcli/git": "^3.0.0", - "@npmcli/installed-package-contents": "^1.0.7", - "@npmcli/promise-spawn": "^3.0.0", - "@npmcli/run-script": "^4.1.0", - "cacache": "^16.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "infer-owner": "^1.0.4", - "minipass": "^3.1.6", - "mkdirp": "^1.0.4", - "npm-package-arg": "^9.0.0", - "npm-packlist": "^5.1.0", - "npm-pick-manifest": "^7.0.0", - "npm-registry-fetch": "^13.0.1", - "proc-log": "^2.0.0", - "promise-retry": "^2.0.1", - "read-package-json": "^5.0.0", - "read-package-json-fast": "^2.0.3", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11" - } - }, - "parse-conflict-json": { - "version": "2.0.2", - "bundled": true, - "dev": true, - "requires": { - "json-parse-even-better-errors": "^2.3.1", - "just-diff": "^5.0.1", - "just-diff-apply": "^5.2.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "proc-log": { - "version": "2.0.1", - "bundled": true, - "dev": true - }, - "promise-all-reject-late": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "promise-call-limit": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "promise-inflight": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "promise-retry": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "requires": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - } - }, - "promzard": { - "version": "0.3.0", - "bundled": true, - "dev": true, - "requires": { - "read": "1" - } - }, - "qrcode-terminal": { - "version": "0.12.0", - "bundled": true, - "dev": true - }, - "read": { - "version": "1.0.7", - "bundled": true, - "dev": true, - "requires": { - "mute-stream": "~0.0.4" - } - }, - "read-cmd-shim": { - "version": "3.0.0", - "bundled": true, - "dev": true - }, - "read-package-json": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "requires": { - "glob": "^8.0.1", - "json-parse-even-better-errors": "^2.3.1", - "normalize-package-data": "^4.0.0", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "read-package-json-fast": { - "version": "2.0.3", - "bundled": true, - "dev": true, - "requires": { - "json-parse-even-better-errors": "^2.3.0", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "readable-stream": { - "version": "3.6.0", - "bundled": true, - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdir-scoped-modules": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "requires": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" - } - }, - "retry": { - "version": "0.12.0", - "bundled": true, - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "requires": { - "glob": "^7.1.3" - }, - "dependencies": { - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "glob": { - "version": "7.2.3", - "bundled": true, - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.1.2", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "safe-buffer": { - "version": "5.2.1", - "bundled": true, - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "7.3.7", - "bundled": true, - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "bundled": true, - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - } - } - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "signal-exit": { - "version": "3.0.7", - "bundled": true, - "dev": true - }, - "smart-buffer": { - "version": "4.2.0", - "bundled": true, - "dev": true - }, - "socks": { - "version": "2.6.2", - "bundled": true, - "dev": true, - "requires": { - "ip": "^1.1.5", - "smart-buffer": "^4.2.0" - } - }, - "socks-proxy-agent": { - "version": "7.0.0", - "bundled": true, - "dev": true, - "requires": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - } - }, - "spdx-correct": { - "version": "3.1.1", - "bundled": true, - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "bundled": true, - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.11", - "bundled": true, - "dev": true - }, - "ssri": { - "version": "9.0.1", - "bundled": true, - "dev": true, - "requires": { - "minipass": "^3.1.1" - } - }, - "string_decoder": { - "version": "1.3.0", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-width": { - "version": "4.2.3", - "bundled": true, - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "bundled": true, - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "tar": { - "version": "6.1.11", - "bundled": true, - "dev": true, - "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - } - }, - "text-table": { - "version": "0.2.0", - "bundled": true, - "dev": true - }, - "tiny-relative-date": { - "version": "1.3.0", - "bundled": true, - "dev": true - }, - "treeverse": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "unique-filename": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "requires": { - "unique-slug": "^2.0.0" - } - }, - "unique-slug": { - "version": "2.0.2", - "bundled": true, - "dev": true, - "requires": { - "imurmurhash": "^0.1.4" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "validate-npm-package-name": { - "version": "4.0.0", - "bundled": true, - "dev": true, - "requires": { - "builtins": "^5.0.0" - } - }, - "walk-up-path": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "wcwidth": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "defaults": "^1.0.3" - } - }, - "which": { - "version": "2.0.2", - "bundled": true, - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wide-align": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "write-file-atomic": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - }, - "yallist": { - "version": "4.0.0", - "bundled": true, - "dev": true - } - } - }, - "npm-run-path": { - "version": "4.0.1", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "nwsapi": { - "version": "2.2.0" - }, - "object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true - }, - "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" - }, - "object-is": { - "version": "1.1.5", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "object-keys": { - "version": "1.1.1" - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "once": { - "version": "1.4.0", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.9.1", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "requires": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "dev": true - }, - "p-cancelable": { - "version": "2.1.1", - "dev": true - }, - "p-each-series": { - "version": "2.2.0", - "dev": true - }, - "p-filter": { - "version": "2.1.0", - "dev": true, - "requires": { - "p-map": "^2.0.0" - }, - "dependencies": { - "p-map": { - "version": "2.1.0", - "dev": true - } - } - }, - "p-is-promise": { - "version": "3.0.0", - "dev": true - }, - "p-limit": { - "version": "2.3.0", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-map": { - "version": "4.0.0", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-reduce": { - "version": "2.1.0", - "dev": true - }, - "p-retry": { - "version": "4.6.1", - "dev": true, - "requires": { - "@types/retry": "^0.12.0", - "retry": "^0.13.1" - } - }, - "p-try": { - "version": "2.2.0", - "dev": true - }, - "pako": { - "version": "0.2.9" - }, - "parent-module": { - "version": "1.0.1", - "dev": true, - "requires": { - "callsites": "^3.0.0" - }, - "dependencies": { - "callsites": { - "version": "3.1.0", - "dev": true - } - } - }, - "parse-json": { - "version": "4.0.0", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "parse-passwd": { - "version": "1.0.0", - "dev": true - }, - "parse-path": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", - "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", - "dev": true, - "requires": { - "protocols": "^2.0.0" - } - }, - "parse-url": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", - "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", - "dev": true, - "requires": { - "parse-path": "^7.0.0" - } - }, - "parse5": { - "version": "6.0.1" - }, - "path-exists": { - "version": "4.0.0", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "dev": true - }, - "path-parse": { - "version": "1.0.7" - }, - "path-type": { - "version": "4.0.0", - "dev": true - }, - "pdfkit": { - "version": "0.12.3", - "requires": { - "crypto-js": "^4.0.0", - "fontkit": "^1.8.1", - "linebreak": "^1.0.2", - "png-js": "^1.0.0" - } - }, - "pdfmake": { - "version": "0.2.2", - "requires": { - "@foliojs-fork/linebreak": "^1.1.1", - "@foliojs-fork/pdfkit": "^0.12.3", - "iconv-lite": "^0.6.3", - "svg-to-pdfkit": "^0.1.8", - "xmldoc": "^1.1.2" - }, - "dependencies": { - "iconv-lite": { - "version": "0.6.3", - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - } - } - }, - "pegjs": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", - "integrity": "sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==", - "dev": true - }, - "performance-now": { - "version": "2.1.0" - }, - "picocolors": { - "version": "1.0.0" - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", - "dev": true - }, - "pify": { - "version": "2.3.0", - "dev": true - }, - "pkg-conf": { - "version": "2.1.0", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "load-json-file": "^4.0.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "dev": true - } - } - }, - "png-js": { - "version": "1.0.0" - }, - "polylabel": { - "version": "1.1.0", - "requires": { - "tinyqueue": "^2.0.3" - } - }, - "postcss": { - "version": "8.4.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", - "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", - "requires": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "postcss-attribute-case-insensitive": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", - "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.10" - } - }, - "postcss-calc": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", - "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.9", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-clamp": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", - "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-color-functional-notation": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", - "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-color-hex-alpha": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", - "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-color-rebeccapurple": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", - "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-colormin": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.0.tgz", - "integrity": "sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==", - "dev": true, - "requires": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0", - "colord": "^2.9.1", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-convert-values": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.2.tgz", - "integrity": "sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g==", - "dev": true, - "requires": { - "browserslist": "^4.20.3", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-custom-media": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", - "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-custom-properties": { - "version": "12.1.9", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.9.tgz", - "integrity": "sha512-/E7PRvK8DAVljBbeWrcEQJPG72jaImxF3vvCNFwv9cC8CzigVoNIpeyfnJzphnN3Fd8/auBf5wvkw6W9MfmTyg==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-custom-selectors": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", - "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.4" - } - }, - "postcss-dir-pseudo-class": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", - "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.10" - } - }, - "postcss-discard-comments": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", - "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", - "dev": true, - "requires": {} - }, - "postcss-discard-duplicates": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "dev": true, - "requires": {} - }, - "postcss-discard-empty": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", - "dev": true, - "requires": {} - }, - "postcss-discard-overridden": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", - "dev": true, - "requires": {} - }, - "postcss-double-position-gradients": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", - "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", - "dev": true, - "requires": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-env-function": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", - "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-focus-visible": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", - "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.9" - } - }, - "postcss-focus-within": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", - "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.9" - } - }, - "postcss-font-variant": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", - "dev": true, - "requires": {} - }, - "postcss-gap-properties": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", - "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", - "dev": true, - "requires": {} - }, - "postcss-image-set-function": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", - "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-import": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.0.0.tgz", - "integrity": "sha512-Y20shPQ07RitgBGv2zvkEAu9bqvrD77C9axhj/aA1BQj4czape2MdClCExvB27EwYEJdGgKZBpKanb0t1rK2Kg==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - } - }, - "postcss-initial": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", - "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", - "dev": true, - "requires": {} - }, - "postcss-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz", - "integrity": "sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==", - "dev": true, - "requires": { - "camelcase-css": "^2.0.1" - } - }, - "postcss-lab-function": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", - "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", - "dev": true, - "requires": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", - "dev": true, - "requires": { - "lilconfig": "^2.0.5", - "yaml": "^1.10.2" - } - }, - "postcss-logical": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", - "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", - "dev": true, - "requires": {} - }, - "postcss-media-minmax": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", - "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", - "dev": true, - "requires": {} - }, - "postcss-media-query-parser": { - "version": "0.2.3", - "dev": true - }, - "postcss-merge-longhand": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.6.tgz", - "integrity": "sha512-6C/UGF/3T5OE2CEbOuX7iNO63dnvqhGZeUnKkDeifebY0XqkkvrctYSZurpNE902LDf2yKwwPFgotnfSoPhQiw==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^5.1.0" - } - }, - "postcss-merge-rules": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.2.tgz", - "integrity": "sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ==", - "dev": true, - "requires": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^3.1.0", - "postcss-selector-parser": "^6.0.5" - } - }, - "postcss-minify-font-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", - "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-gradients": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", - "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", - "dev": true, - "requires": { - "colord": "^2.9.1", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-params": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz", - "integrity": "sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg==", - "dev": true, - "requires": { - "browserslist": "^4.16.6", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-selectors": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", - "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.5" - } - }, - "postcss-nested": { - "version": "5.0.6", - "requires": { - "postcss-selector-parser": "^6.0.6" - } - }, - "postcss-nesting": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", - "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", - "dev": true, - "requires": { - "@csstools/selector-specificity": "^2.0.0", - "postcss-selector-parser": "^6.0.10" - } - }, - "postcss-normalize-charset": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", - "dev": true, - "requires": {} - }, - "postcss-normalize-display-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", - "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-positions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", - "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-repeat-style": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", - "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-string": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", - "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-timing-functions": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", - "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-unicode": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz", - "integrity": "sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ==", - "dev": true, - "requires": { - "browserslist": "^4.16.6", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", - "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", - "dev": true, - "requires": { - "normalize-url": "^6.0.1", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-whitespace": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", - "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-opacity-percentage": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.2.tgz", - "integrity": "sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w==", - "dev": true - }, - "postcss-ordered-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", - "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", - "dev": true, - "requires": { - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-overflow-shorthand": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", - "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-page-break": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", - "dev": true, - "requires": {} - }, - "postcss-place": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", - "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-preset-env": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.2.tgz", - "integrity": "sha512-rSMUEaOCnovKnwc5LvBDHUDzpGP+nrUeWZGWt9M72fBvckCi45JmnJigUr4QG4zZeOHmOCNCZnd2LKDvP++ZuQ==", - "dev": true, - "requires": { - "@csstools/postcss-cascade-layers": "^1.1.0", - "@csstools/postcss-color-function": "^1.1.1", - "@csstools/postcss-font-format-keywords": "^1.0.1", - "@csstools/postcss-hwb-function": "^1.0.2", - "@csstools/postcss-ic-unit": "^1.0.1", - "@csstools/postcss-is-pseudo-class": "^2.0.7", - "@csstools/postcss-nested-calc": "^1.0.0", - "@csstools/postcss-normalize-display-values": "^1.0.1", - "@csstools/postcss-oklab-function": "^1.1.1", - "@csstools/postcss-progressive-custom-properties": "^1.3.0", - "@csstools/postcss-stepped-value-functions": "^1.0.1", - "@csstools/postcss-text-decoration-shorthand": "^1.0.0", - "@csstools/postcss-trigonometric-functions": "^1.0.2", - "@csstools/postcss-unset-value": "^1.0.2", - "autoprefixer": "^10.4.11", - "browserslist": "^4.21.3", - "css-blank-pseudo": "^3.0.3", - "css-has-pseudo": "^3.0.4", - "css-prefers-color-scheme": "^6.0.3", - "cssdb": "^7.0.1", - "postcss-attribute-case-insensitive": "^5.0.2", - "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^4.2.4", - "postcss-color-hex-alpha": "^8.0.4", - "postcss-color-rebeccapurple": "^7.1.1", - "postcss-custom-media": "^8.0.2", - "postcss-custom-properties": "^12.1.9", - "postcss-custom-selectors": "^6.0.3", - "postcss-dir-pseudo-class": "^6.0.5", - "postcss-double-position-gradients": "^3.1.2", - "postcss-env-function": "^4.0.6", - "postcss-focus-visible": "^6.0.4", - "postcss-focus-within": "^5.0.4", - "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^3.0.5", - "postcss-image-set-function": "^4.0.7", - "postcss-initial": "^4.0.1", - "postcss-lab-function": "^4.2.1", - "postcss-logical": "^5.0.4", - "postcss-media-minmax": "^5.0.0", - "postcss-nesting": "^10.2.0", - "postcss-opacity-percentage": "^1.1.2", - "postcss-overflow-shorthand": "^3.0.4", - "postcss-page-break": "^3.0.4", - "postcss-place": "^7.0.5", - "postcss-pseudo-class-any-link": "^7.1.6", - "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^6.0.1", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-pseudo-class-any-link": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", - "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.10" - } - }, - "postcss-reduce-initial": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz", - "integrity": "sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw==", - "dev": true, - "requires": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0" - } - }, - "postcss-reduce-transforms": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", - "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-replace-overflow-wrap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", - "dev": true, - "requires": {} - }, - "postcss-reporter": { - "version": "7.0.5", - "dev": true, - "requires": { - "picocolors": "^1.0.0", - "thenby": "^1.3.4" - } - }, - "postcss-resolve-nested-selector": { - "version": "0.1.1", - "dev": true - }, - "postcss-safe-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", - "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", - "dev": true, - "requires": {} - }, - "postcss-selector-not": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", - "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.10" - } - }, - "postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "postcss-svgo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", - "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0", - "svgo": "^2.7.0" - } - }, - "postcss-unique-selectors": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", - "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.5" - } - }, - "postcss-value-parser": { - "version": "4.2.0", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "dev": true - }, - "prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", - "dev": true - }, - "prettier-linter-helpers": { - "version": "1.0.0", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "prettier-plugin-organize-imports": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.1.1.tgz", - "integrity": "sha512-6bHIQzybqA644h0WGUW3gpWEVbMBvzui5wCMRBi7qA++d5ov2xjjfDk8pxJJ/ardfZrGAwizKMq/fQMFdJ+0Zw==", - "dev": true, - "requires": {} - }, - "pretty-bytes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.0.0.tgz", - "integrity": "sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg==", - "dev": true - }, - "printj": { - "version": "1.1.2" - }, - "process-nextick-args": { - "version": "2.0.1" - }, - "protocols": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", - "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==", - "dev": true - }, - "psl": { - "version": "1.8.0" - }, - "pump": { - "version": "3.0.0", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1" - }, - "q": { - "version": "1.5.1", - "dev": true - }, - "quick-lru": { - "version": "4.0.1", - "dev": true - }, - "quote-stream": { - "version": "1.0.2", - "requires": { - "buffer-equal": "0.0.1", - "minimist": "^1.1.3", - "through2": "^2.0.0" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, - "raf": { - "version": "3.4.1", - "requires": { - "performance-now": "^2.1.0" - } - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - } - } - }, - "read-cache": { - "version": "1.0.0", - "dev": true, - "requires": { - "pify": "^2.3.0" - } - }, - "read-pkg": { - "version": "5.2.0", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "hosted-git-info": { - "version": "2.8.9", - "dev": true - }, - "normalize-package-data": { - "version": "2.5.0", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "parse-json": { - "version": "5.2.0", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "semver": { - "version": "5.7.1", - "dev": true - }, - "type-fest": { - "version": "0.6.0", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "type-fest": { - "version": "0.8.1", - "dev": true - } - } - }, - "readable-stream": { - "version": "2.3.7", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "redent": { - "version": "3.0.0", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, - "redeyed": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", - "integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=", - "dev": true, - "requires": { - "esprima": "~4.0.0" - } - }, - "redux": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz", - "integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==", - "requires": { - "@babel/runtime": "^7.9.2" - } - }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "regenerate-unicode-properties": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", - "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", - "dev": true, - "requires": { - "regenerate": "^1.4.2" - } - }, - "regenerator-runtime": { - "version": "0.13.7" - }, - "regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - } - }, - "regexpp": { - "version": "3.2.0", - "dev": true - }, - "regexpu-core": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.1.0.tgz", - "integrity": "sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA==", - "dev": true, - "requires": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.0.1", - "regjsgen": "^0.6.0", - "regjsparser": "^0.8.2", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - } - }, - "registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, - "regjsgen": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", - "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", - "dev": true - }, - "regjsparser": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", - "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true - } - } - }, - "regression": { - "version": "2.0.1" - }, - "require-directory": { - "version": "2.1.1", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-alpn": { - "version": "1.2.1", - "dev": true - }, - "resolve-dir": { - "version": "1.0.1", - "dev": true, - "requires": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "dev": true - }, - "resolve-global": { - "version": "1.0.0", - "dev": true, - "requires": { - "global-dirs": "^0.1.1" - } - }, - "responselike": { - "version": "2.0.0", - "dev": true, - "requires": { - "lowercase-keys": "^2.0.0" - } - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "restructure": { - "version": "0.5.4", - "requires": { - "browserify-optional": "^1.0.0" - } - }, - "retry": { - "version": "0.13.1", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "dev": true - }, - "rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, - "rgbcolor": { - "version": "1.0.1" - }, - "rimraf": { - "version": "3.0.2", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rollup": { - "version": "2.75.7", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.7.tgz", - "integrity": "sha512-VSE1iy0eaAYNCxEXaleThdFXqZJ42qDBatAwrfnPlENEZ8erQ+0LYX4JXOLPceWfZpV1VtZwZ3dFCuOZiSyFtQ==", - "dev": true, - "requires": { - "fsevents": "~2.3.2" - } - }, - "rollup-plugin-terser": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", - "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "jest-worker": "^26.2.1", - "serialize-javascript": "^4.0.0", - "terser": "^5.0.0" - } - }, - "run-async": { - "version": "2.4.1", - "dev": true - }, - "run-parallel": { - "version": "1.1.9", - "dev": true - }, - "rxjs": { - "version": "6.6.2", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.1.2" - }, - "safer-buffer": { - "version": "2.1.2" - }, - "sax": { - "version": "1.2.4" - }, - "saxes": { - "version": "5.0.1", - "requires": { - "xmlchars": "^2.2.0" - } - }, - "scope-analyzer": { - "version": "2.1.1", - "requires": { - "array-from": "^2.1.1", - "dash-ast": "^1.0.0", - "es6-map": "^0.1.5", - "es6-set": "^0.1.5", - "es6-symbol": "^3.1.1", - "estree-is-function": "^1.0.0", - "get-assigned-identifiers": "^1.1.0" - } - }, - "semantic-release": { - "version": "19.0.5", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-19.0.5.tgz", - "integrity": "sha512-NMPKdfpXTnPn49FDogMBi36SiBfXkSOJqCkk0E4iWOY1tusvvgBwqUmxTX1kmlT6kIYed9YwNKD1sfPpqa5yaA==", - "dev": true, - "requires": { - "@semantic-release/commit-analyzer": "^9.0.2", - "@semantic-release/error": "^3.0.0", - "@semantic-release/github": "^8.0.0", - "@semantic-release/npm": "^9.0.0", - "@semantic-release/release-notes-generator": "^10.0.0", - "aggregate-error": "^3.0.0", - "cosmiconfig": "^7.0.0", - "debug": "^4.0.0", - "env-ci": "^5.0.0", - "execa": "^5.0.0", - "figures": "^3.0.0", - "find-versions": "^4.0.0", - "get-stream": "^6.0.0", - "git-log-parser": "^1.2.0", - "hook-std": "^2.0.0", - "hosted-git-info": "^4.0.0", - "lodash": "^4.17.21", - "marked": "^4.0.10", - "marked-terminal": "^5.0.0", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", - "p-reduce": "^2.0.0", - "read-pkg-up": "^7.0.0", - "resolve-from": "^5.0.0", - "semver": "^7.3.2", - "semver-diff": "^3.1.1", - "signale": "^1.2.1", - "yargs": "^16.2.0" - }, - "dependencies": { - "figures": { - "version": "3.2.0", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "get-stream": { - "version": "6.0.1", - "dev": true - }, - "semver": { - "version": "7.3.5", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "semver": { - "version": "6.3.0", - "dev": true - }, - "semver-diff": { - "version": "3.1.1", - "dev": true, - "requires": { - "semver": "^6.3.0" - } - }, - "semver-regex": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz", - "integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==", - "dev": true - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "shallow-copy": { - "version": "0.0.1" - }, - "shebang-command": { - "version": "2.0.0", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "signale": { - "version": "1.4.0", - "dev": true, - "requires": { - "chalk": "^2.3.2", - "figures": "^2.0.0", - "pkg-conf": "^2.1.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, - "slash": { - "version": "3.0.0", - "dev": true - }, - "slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "source-map": { - "version": "0.5.7" - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "sourcemap-codec": { - "version": "1.4.8" - }, - "spawn-error-forwarder": { - "version": "1.0.0", - "dev": true - }, - "spdx-correct": { - "version": "3.1.1", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.7", - "dev": true - }, - "split": { - "version": "1.0.1", - "dev": true, - "requires": { - "through": "2" - } - }, - "split2": { - "version": "3.2.2", - "dev": true, - "requires": { - "readable-stream": "^3.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "ssf": { - "version": "0.11.2", - "requires": { - "frac": "~1.1.2" - } - }, - "stable": { - "version": "0.1.8", - "dev": true - }, - "stackblur-canvas": { - "version": "2.5.0" - }, - "static-eval": { - "version": "2.1.0", - "requires": { - "escodegen": "^1.11.1" - }, - "dependencies": { - "escodegen": { - "version": "1.14.3", - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, - "levn": { - "version": "0.3.0", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2" - }, - "source-map": { - "version": "0.6.1", - "optional": true - }, - "type-check": { - "version": "0.3.2", - "requires": { - "prelude-ls": "~1.1.2" - } - } - } - }, - "static-module": { - "version": "3.0.4", - "requires": { - "acorn-node": "^1.3.0", - "concat-stream": "~1.6.0", - "convert-source-map": "^1.5.1", - "duplexer2": "~0.1.4", - "escodegen": "^1.11.1", - "has": "^1.0.1", - "magic-string": "0.25.1", - "merge-source-map": "1.0.4", - "object-inspect": "^1.6.0", - "readable-stream": "~2.3.3", - "scope-analyzer": "^2.0.1", - "shallow-copy": "~0.0.1", - "static-eval": "^2.0.5", - "through2": "~2.0.3" - }, - "dependencies": { - "escodegen": { - "version": "1.14.3", - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, - "levn": { - "version": "0.3.0", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2" - }, - "source-map": { - "version": "0.6.1", - "optional": true - }, - "through2": { - "version": "2.0.5", - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "type-check": { - "version": "0.3.2", - "requires": { - "prelude-ls": "~1.1.2" - } - } - } - }, - "stencil-wormhole": { - "version": "3.4.1" - }, - "stream-combiner2": { - "version": "1.1.1", - "dev": true, - "requires": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" - } - }, - "string_decoder": { - "version": "1.1.1", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "string-argv": { - "version": "0.3.1", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "string.prototype.matchall": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", - "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.1", - "side-channel": "^1.0.4" - } - }, - "string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dev": true, - "requires": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "dependencies": { - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", - "dev": true - } - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "dev": true - }, - "strip-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", - "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "dev": true - }, - "strip-indent": { - "version": "3.0.0", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "style-mod": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz", - "integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==" - }, - "style-search": { - "version": "0.1.0", - "dev": true - }, - "stylehacks": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", - "integrity": "sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==", - "dev": true, - "requires": { - "browserslist": "^4.16.6", - "postcss-selector-parser": "^6.0.4" - } - }, - "stylelint": { - "version": "14.13.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.13.0.tgz", - "integrity": "sha512-NJSAdloiAB/jgVJKxMR90mWlctvmeBFGFVUvyKngi9+j/qPSJ5ZB+u8jOmGbLTnS7OHrII9NFGehPRyar8U5vg==", - "dev": true, - "requires": { - "@csstools/selector-specificity": "^2.0.2", - "balanced-match": "^2.0.0", - "colord": "^2.9.3", - "cosmiconfig": "^7.0.1", - "css-functions-list": "^3.1.0", - "debug": "^4.3.4", - "fast-glob": "^3.2.12", - "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^6.0.1", - "global-modules": "^2.0.0", - "globby": "^11.1.0", - "globjoin": "^0.1.4", - "html-tags": "^3.2.0", - "ignore": "^5.2.0", - "import-lazy": "^4.0.0", - "imurmurhash": "^0.1.4", - "is-plain-object": "^5.0.0", - "known-css-properties": "^0.25.0", - "mathml-tag-names": "^2.1.3", - "meow": "^9.0.0", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.16", - "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.10", - "postcss-value-parser": "^4.2.0", - "resolve-from": "^5.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "style-search": "^0.1.0", - "supports-hyperlinks": "^2.3.0", - "svg-tags": "^1.0.0", - "table": "^6.8.0", - "v8-compile-cache": "^2.3.0", - "write-file-atomic": "^4.0.2" - }, - "dependencies": { - "balanced-match": { - "version": "2.0.0", - "dev": true - }, - "global-modules": { - "version": "2.0.0", - "dev": true, - "requires": { - "global-prefix": "^3.0.0" - } - }, - "global-prefix": { - "version": "3.0.0", - "dev": true, - "requires": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - } - }, - "meow": { - "version": "9.0.0", - "dev": true, - "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - } - } - } - }, - "stylelint-config-recommended": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-9.0.0.tgz", - "integrity": "sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==", - "dev": true, - "requires": {} - }, - "stylelint-config-standard": { - "version": "28.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-28.0.0.tgz", - "integrity": "sha512-q/StuowDdDmFCravzGHAwgS9pjX0bdOQUEBBDIkIWsQuYGgYz/xsO8CM6eepmIQ1fc5bKdDVimlJZ6MoOUcJ5Q==", - "dev": true, - "requires": { - "stylelint-config-recommended": "^9.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" - }, - "svg-tags": { - "version": "1.0.0", - "dev": true - }, - "svg-to-pdfkit": { - "version": "0.1.8", - "requires": { - "pdfkit": ">=0.8.1" - } - }, - "svgo": { - "version": "2.8.0", - "dev": true, - "requires": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" - }, - "dependencies": { - "commander": { - "version": "7.2.0", - "dev": true - }, - "css-select": { - "version": "4.1.3", - "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^5.0.0", - "domhandler": "^4.2.0", - "domutils": "^2.6.0", - "nth-check": "^2.0.0" - } - }, - "css-what": { - "version": "5.0.1", - "dev": true - }, - "dom-serializer": { - "version": "1.3.2", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - } - }, - "domelementtype": { - "version": "2.2.0", - "dev": true - }, - "domhandler": { - "version": "4.2.0", - "dev": true, - "requires": { - "domelementtype": "^2.2.0" - } - }, - "domutils": { - "version": "2.7.0", - "dev": true, - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } - }, - "nth-check": { - "version": "2.0.1", - "dev": true, - "requires": { - "boolbase": "^1.0.0" - } - } - } - }, - "symbol-tree": { - "version": "3.2.4" - }, - "table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", - "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - } - } - }, - "tailwindcss": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.1.8.tgz", - "integrity": "sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==", - "dev": true, - "requires": { - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "color-name": "^1.1.4", - "detective": "^5.2.1", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.2.11", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "lilconfig": "^2.0.6", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.14", - "postcss-import": "^14.1.0", - "postcss-js": "^4.0.0", - "postcss-load-config": "^3.1.4", - "postcss-nested": "5.0.6", - "postcss-selector-parser": "^6.0.10", - "postcss-value-parser": "^4.2.0", - "quick-lru": "^5.1.1", - "resolve": "^1.22.1" - }, - "dependencies": { - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "glob-parent": { - "version": "6.0.2", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "lilconfig": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", - "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", - "dev": true - }, - "postcss-import": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", - "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - } - }, - "quick-lru": { - "version": "5.1.1", - "dev": true - } - } - }, - "temp-dir": { - "version": "2.0.0", - "dev": true - }, - "tempy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-1.0.1.tgz", - "integrity": "sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==", - "dev": true, - "requires": { - "del": "^6.0.0", - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" - }, - "dependencies": { - "type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", - "dev": true - } - } - }, - "terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", - "dev": true, - "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", - "dev": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - }, - "text-extensions": { - "version": "1.9.0", - "dev": true - }, - "text-table": { - "version": "0.2.0", - "dev": true - }, - "thenby": { - "version": "1.3.4", - "dev": true - }, - "through": { - "version": "2.3.8" - }, - "through2": { - "version": "4.0.2", - "dev": true, - "requires": { - "readable-stream": "3" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "tiny-inflate": { - "version": "1.0.3" - }, - "tinyqueue": { - "version": "2.0.3" - }, - "tmp": { - "version": "0.0.33", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tough-cookie": { - "version": "4.0.0", - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - } - }, - "tr46": { - "version": "2.1.0", - "requires": { - "punycode": "^2.1.1" - } - }, - "traverse": { - "version": "0.6.6", - "dev": true - }, - "trim-newlines": { - "version": "3.0.1", - "dev": true - }, - "ts-node": { - "version": "10.8.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.1.tgz", - "integrity": "sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g==", - "dev": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "dependencies": { - "acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", - "dev": true - }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - } - } - }, - "tslib": { - "version": "1.13.0", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "type": { - "version": "1.2.0" - }, - "type-check": { - "version": "0.4.0", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-fest": { - "version": "0.18.1", - "dev": true - }, - "typedarray": { - "version": "0.0.6" - }, - "typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", - "dev": true - }, - "uglify-js": { - "version": "3.14.5", - "dev": true, - "optional": true - }, - "unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - } - }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true - }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "dev": true - }, - "unicode-properties": { - "version": "1.3.1", - "requires": { - "base64-js": "^1.3.0", - "unicode-trie": "^2.0.0" - } - }, - "unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", - "dev": true - }, - "unicode-trie": { - "version": "2.0.0", - "requires": { - "pako": "^0.2.5", - "tiny-inflate": "^1.0.0" - } - }, - "unique-string": { - "version": "2.0.0", - "dev": true, - "requires": { - "crypto-random-string": "^2.0.0" - } - }, - "universal-user-agent": { - "version": "6.0.0", - "dev": true - }, - "universalify": { - "version": "0.1.2" - }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", - "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url-join": { - "version": "4.0.1", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2" - }, - "v8-compile-cache": { - "version": "2.3.0", - "dev": true - }, - "v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "vite": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-2.8.6.tgz", - "integrity": "sha512-e4H0QpludOVKkmOsRyqQ7LTcMUDF3mcgyNU4lmi0B5JUbe0ZxeBBl8VoZ8Y6Rfn9eFKYtdXNPcYK97ZwH+K2ug==", - "dev": true, - "requires": { - "esbuild": "^0.14.14", - "fsevents": "~2.3.2", - "postcss": "^8.4.6", - "resolve": "^1.22.0", - "rollup": "^2.59.0" - } - }, - "vite-plugin-pwa": { - "version": "0.12.8", - "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.12.8.tgz", - "integrity": "sha512-pSiFHmnJGMQJJL8aJzQ8SaraZBSBPMGvGUkCNzheIq9UQCEk/eP3UmANNmS9eupuhIpTK8AdxTOHcaMcAqAbCA==", - "dev": true, - "requires": { - "debug": "^4.3.4", - "fast-glob": "^3.2.11", - "pretty-bytes": "^6.0.0", - "rollup": "^2.75.7", - "workbox-build": "^6.5.3", - "workbox-window": "^6.5.3" - } - }, - "w3c-hr-time": { - "version": "1.0.2", - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-keyname": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.4.tgz", - "integrity": "sha512-tOhfEwEzFLJzf6d1ZPkYfGj+FWhIpBux9ppoP3rlclw3Z0BZv3N7b7030Z1kYth+6rDuAsXUFr+d0VE6Ed1ikw==" - }, - "w3c-xmlserializer": { - "version": "2.0.0", - "requires": { - "xml-name-validator": "^3.0.0" - } - }, - "wavesurfer.js": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-6.3.0.tgz", - "integrity": "sha512-x7efObHMHY3nwqWzIC0yeZTo0u/aC9T5Av6KhSdhTlsgtKdTG7JAE3mNqnYBXUjq0yGYfTB9F85/pks9830Vjw==" - }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "requires": { - "defaults": "^1.0.3" - } - }, - "webidl-conversions": { - "version": "6.1.0" - }, - "whatwg-encoding": { - "version": "1.0.5", - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0" - }, - "whatwg-url": { - "version": "8.7.0", - "requires": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - } - }, - "which": { - "version": "1.3.1", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", - "dev": true - }, - "wmf": { - "version": "1.0.2" - }, - "word": { - "version": "0.4.0", - "requires": { - "cfb": "^1.2.0", - "jsdom": "^16.2.2" - } - }, - "word-wrap": { - "version": "1.2.3" - }, - "wordwrap": { - "version": "1.0.0", - "dev": true - }, - "workbox-background-sync": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz", - "integrity": "sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==", - "dev": true, - "requires": { - "idb": "^7.0.1", - "workbox-core": "6.5.4" - } - }, - "workbox-broadcast-update": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz", - "integrity": "sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==", - "dev": true, - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-build": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.5.4.tgz", - "integrity": "sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==", - "dev": true, - "requires": { - "@apideck/better-ajv-errors": "^0.3.1", - "@babel/core": "^7.11.1", - "@babel/preset-env": "^7.11.0", - "@babel/runtime": "^7.11.2", - "@rollup/plugin-babel": "^5.2.0", - "@rollup/plugin-node-resolve": "^11.2.1", - "@rollup/plugin-replace": "^2.4.1", - "@surma/rollup-plugin-off-main-thread": "^2.2.3", - "ajv": "^8.6.0", - "common-tags": "^1.8.0", - "fast-json-stable-stringify": "^2.1.0", - "fs-extra": "^9.0.1", - "glob": "^7.1.6", - "lodash": "^4.17.20", - "pretty-bytes": "^5.3.0", - "rollup": "^2.43.1", - "rollup-plugin-terser": "^7.0.0", - "source-map": "^0.8.0-beta.0", - "stringify-object": "^3.3.0", - "strip-comments": "^2.0.1", - "tempy": "^0.6.0", - "upath": "^1.2.0", - "workbox-background-sync": "6.5.4", - "workbox-broadcast-update": "6.5.4", - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-google-analytics": "6.5.4", - "workbox-navigation-preload": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-range-requests": "6.5.4", - "workbox-recipes": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4", - "workbox-streams": "6.5.4", - "workbox-sw": "6.5.4", - "workbox-window": "6.5.4" - }, - "dependencies": { - "@apideck/better-ajv-errors": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", - "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", - "dev": true, - "requires": { - "json-schema": "^0.4.0", - "jsonpointer": "^5.0.0", - "leven": "^3.1.0" - } - }, - "pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true - }, - "source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "dev": true, - "requires": { - "whatwg-url": "^7.0.0" - } - }, - "tempy": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", - "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", - "dev": true, - "requires": { - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", - "dev": true - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } - } - }, - "workbox-cacheable-response": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz", - "integrity": "sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==", - "dev": true, - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-core": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.5.4.tgz", - "integrity": "sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==", - "dev": true - }, - "workbox-expiration": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.5.4.tgz", - "integrity": "sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==", - "dev": true, - "requires": { - "idb": "^7.0.1", - "workbox-core": "6.5.4" - } - }, - "workbox-google-analytics": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz", - "integrity": "sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==", - "dev": true, - "requires": { - "workbox-background-sync": "6.5.4", - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "workbox-navigation-preload": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz", - "integrity": "sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==", - "dev": true, - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-precaching": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.5.4.tgz", - "integrity": "sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==", - "dev": true, - "requires": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "workbox-range-requests": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz", - "integrity": "sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==", - "dev": true, - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-recipes": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.5.4.tgz", - "integrity": "sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==", - "dev": true, - "requires": { - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "workbox-routing": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.5.4.tgz", - "integrity": "sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==", - "dev": true, - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-strategies": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.5.4.tgz", - "integrity": "sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==", - "dev": true, - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-streams": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.5.4.tgz", - "integrity": "sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==", - "dev": true, - "requires": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4" - } - }, - "workbox-sw": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.5.4.tgz", - "integrity": "sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==", - "dev": true - }, - "workbox-window": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.5.4.tgz", - "integrity": "sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==", - "dev": true, - "requires": { - "@types/trusted-types": "^2.0.2", - "workbox-core": "6.5.4" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "dev": true - } - } - }, - "wrappy": { - "version": "1.0.2", - "dev": true - }, - "write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - }, - "ws": { - "version": "7.5.5", - "requires": {} - }, - "xlsx": { - "version": "0.17.2", - "requires": { - "adler-32": "~1.2.0", - "cfb": "^1.1.4", - "codepage": "~1.15.0", - "commander": "~2.17.1", - "crc-32": "~1.2.0", - "exit-on-epipe": "~1.0.1", - "fflate": "^0.3.8", - "ssf": "~0.11.2", - "wmf": "~1.0.1", - "word": "~0.4.0" - }, - "dependencies": { - "commander": { - "version": "2.17.1" - } - } - }, - "xml-formatter": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/xml-formatter/-/xml-formatter-2.6.1.tgz", - "integrity": "sha512-dOiGwoqm8y22QdTNI7A+N03tyVfBlQ0/oehAzxIZtwnFAHGeSlrfjF73YQvzSsa/Kt6+YZasKsrdu6OIpuBggw==", - "requires": { - "xml-parser-xo": "^3.2.0" - } - }, - "xml-name-validator": { - "version": "3.0.0" - }, - "xml-parser-xo": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/xml-parser-xo/-/xml-parser-xo-3.2.0.tgz", - "integrity": "sha512-8LRU6cq+d7mVsoDaMhnkkt3CTtAs4153p49fRo+HIB3I1FD1o5CeXRjRH29sQevIfVJIcPjKSsPU/+Ujhq09Rg==" - }, - "xmlchars": { - "version": "2.2.0" - }, - "xmldoc": { - "version": "1.1.2", - "requires": { - "sax": "^1.2.1" - } - }, - "xtend": { - "version": "4.0.2" - }, - "y18n": { - "version": "5.0.5", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "dev": true - }, - "yaml": { - "version": "1.10.2", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.7", - "dev": true - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} diff --git a/package.json b/package.json index e2c2cb63..3b51642c 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { - "name": "castopod-host", - "version": "1.0.0-beta.24", + "name": "castopod", + "version": "2.0.0-next.3", "description": "Castopod Host is an open-source hosting platform made for podcasters who want engage and interact with their audience.", "private": true, "license": "AGPL-3.0-or-later", + "type": "module", "repository": { "type": "git", "url": "https://code.castopod.org/adaures/castopod.git" @@ -12,91 +13,104 @@ "dev": "vite", "build": "tsc && vite build", "serve": "vite preview", - "build:static": "npm run build:icons && npm run build:svg", - "build:icons": "svgo -f app/Resources/icons -o public/assets/icons -r --config=./.svgo.icons.js", - "build:svg": "svgo -f app/Resources/images -o public/assets/images -r --config=./.svgo.js", - "lint": "eslint --ext js,ts app/Resources", - "lint:fix": "eslint --ext js,ts app/Resources --fix", - "lint:css": "stylelint \"app/Resources/**/*.css\"", - "lint:css:fix": "stylelint --fix \"app/Resources/**/*.css\"", - "prettier": "prettier --check --ignore-path .gitignore .", - "prettier:fix": "prettier --write --ignore-path .gitignore .", + "build:static": "pnpm run build:icons && pnpm run build:svg", + "build:icons": "svgo -f resources/icons -o resources/icons -r --config=./.svgo.icons.cjs", + "build:svg": "svgo -f resources/static/images -o resources/static/images -r --config=./.svgo.cjs", + "lint": "eslint", + "lint:fix": "eslint --fix", + "lint:css": "stylelint -f verbose \"resources/**/*.css\"", + "lint:css:fix": "stylelint -f verbose --fix \"resources/**/*.css\"", + "prettier": "prettier --check . --ignore-path ./.gitignore --ignore-path ./docs/.gitignore", + "format": "prettier --write . --ignore-path ./.gitignore --ignore-path ./docs/.gitignore", "typecheck": "tsc", + "all-contributors:add": "all-contributors add", + "all-contributors:generate": "all-contributors generate", "commit": "cz", "release": "semantic-release", - "prepare": "is-ci || husky install" + "prepare": "is-ci || husky" }, "dependencies": { - "@amcharts/amcharts4": "^4.10.29", - "@amcharts/amcharts4-geodata": "^4.1.23", - "@codemirror/commands": "^6.1.2", - "@codemirror/lang-xml": "^6.0.0", - "@codemirror/language": "^6.2.1", - "@codemirror/state": "^6.1.2", - "@floating-ui/dom": "^1.0.2", - "@github/clipboard-copy-element": "^1.1.2", - "@github/hotkey": "^2.0.1", - "@github/markdown-toolbar-element": "^2.1.1", - "@github/time-elements": "^3.1.4", - "@tailwindcss/nesting": "^0.0.0-insiders.565cd3e", - "@vime/core": "^5.3.3", - "choices.js": "^10.1.0", - "codemirror": "^6.0.1", + "@amcharts/amcharts4": "^4.10.40", + "@amcharts/amcharts4-geodata": "^4.1.31", + "@codemirror/commands": "^6.10.2", + "@codemirror/lang-html": "^6.4.11", + "@codemirror/lang-xml": "^6.1.0", + "@codemirror/language": "^6.12.1", + "@codemirror/state": "^6.5.4", + "@codemirror/view": "^6.39.14", + "@floating-ui/dom": "^1.7.5", + "@github/clipboard-copy-element": "^1.3.0", + "@github/hotkey": "^3.1.1", + "@github/markdown-toolbar-element": "^2.2.3", + "@github/relative-time-element": "^5.0.0", + "@patternfly/elements": "^4.3.1", + "@vime/core": "^5.4.1", + "choices.js": "^11.1.0", + "codemirror": "^6.0.2", "flatpickr": "^4.6.13", - "leaflet": "^1.9.2", + "htmlfy": "^1.0.1", + "leaflet": "^1.9.4", "leaflet.markercluster": "^1.5.3", - "lit": "^2.4.0", - "marked": "^4.1.1", - "wavesurfer.js": "^6.3.0", - "xml-formatter": "^2.6.1" + "lit": "^3.3.2", + "marked": "^17.0.3", + "wavesurfer.js": "^7.12.1", + "xml-formatter": "^3.6.7" }, "devDependencies": { - "@commitlint/cli": "^17.1.2", - "@commitlint/config-conventional": "^17.1.0", - "@semantic-release/changelog": "^6.0.1", - "@semantic-release/exec": "^6.0.3", + "@commitlint/cli": "^20.4.2", + "@commitlint/config-conventional": "^20.4.2", + "@csstools/css-tokenizer": "^4.0.0", + "@eslint/eslintrc": "^3.3.3", + "@eslint/js": "^10.0.1", + "@semantic-release/changelog": "^6.0.3", + "@semantic-release/exec": "^7.1.0", "@semantic-release/git": "^10.0.1", - "@semantic-release/gitlab": "^9.4.2", - "@tailwindcss/forms": "^0.5.3", - "@tailwindcss/line-clamp": "^0.4.2", - "@tailwindcss/typography": "^0.5.7", - "@types/leaflet": "^1.8.0", - "@types/marked": "^4.0.7", - "@types/wavesurfer.js": "^6.0.3", - "@typescript-eslint/eslint-plugin": "^5.40.0", - "@typescript-eslint/parser": "^5.40.0", - "all-contributors-cli": "^6.24.0", - "cross-env": "^7.0.3", - "cssnano": "^5.1.13", + "@semantic-release/gitlab": "^13.3.0", + "@tailwindcss/forms": "^0.5.11", + "@tailwindcss/typography": "^0.5.19", + "@types/leaflet": "^1.9.21", + "all-contributors-cli": "^6.26.1", + "commitizen": "^4.3.1", + "conventional-changelog-conventionalcommits": "^9.1.0", + "cross-env": "^10.1.0", + "cssnano": "^7.1.2", "cz-conventional-changelog": "^3.3.0", - "eslint": "^8.25.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-prettier": "^4.2.1", - "husky": "^8.0.1", - "is-ci": "^3.0.1", - "lint-staged": "^13.0.3", - "postcss-import": "^15.0.0", - "postcss-nesting": "^10.2.0", - "postcss-preset-env": "^7.8.2", - "postcss-reporter": "^7.0.5", - "prettier": "2.7.1", - "prettier-plugin-organize-imports": "^3.1.1", - "semantic-release": "^19.0.5", - "stylelint": "^14.13.0", - "stylelint-config-standard": "^28.0.0", - "svgo": "^2.8.0", - "tailwindcss": "^3.1.8", - "typescript": "^4.8.4", - "vite": "2.8.6", - "vite-plugin-pwa": "^0.12.8", - "workbox-build": "^6.5.4", - "workbox-core": "^6.5.4", - "workbox-routing": "^6.5.4", - "workbox-strategies": "^6.5.4" + "eslint": "^10.0.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.5", + "glob": "^13.0.5", + "globals": "^17.3.0", + "husky": "^9.1.7", + "is-ci": "^4.1.0", + "lint-staged": "^16.2.7", + "postcss": "^8.5.6", + "postcss-import": "^16.1.1", + "postcss-nesting": "^14.0.0", + "postcss-preset-env": "^11.1.3", + "postcss-reporter": "^7.1.0", + "prettier": "3.8.1", + "prettier-plugin-organize-imports": "^4.3.0", + "semantic-release": "^25.0.3", + "sharp": "^0.34.5", + "stylelint": "^17.3.0", + "stylelint-config-standard": "^40.0.0", + "svgo": "^4.0.0", + "tailwindcss": "^3.4.19", + "typescript": "~5.9.3", + "typescript-eslint": "^8.56.0", + "vite": "^7.3.1", + "vite-plugin-codeigniter": "^2.0.0", + "vite-plugin-inspect": "^11.3.3", + "vite-plugin-pwa": "^1.2.0", + "vite-plugin-static-copy": "^3.2.0", + "workbox-build": "^7.4.0", + "workbox-core": "^7.4.0", + "workbox-routing": "^7.4.0", + "workbox-strategies": "^7.4.0" }, "lint-staged": { "*.{js,ts,css,md,json}": "prettier --write", - "*.{ts,js}": "eslint --ext js,ts,tsx app/Resources --fix", + "*.{ts,js}": "eslint --fix", "*.css": "stylelint --fix" }, "config": { diff --git a/php-icons.php b/php-icons.php new file mode 100644 index 00000000..afcb4588 --- /dev/null +++ b/php-icons.php @@ -0,0 +1,21 @@ +withPaths([__DIR__ . '/app', __DIR__ . '/themes', __DIR__ . '/resources']) + ->withLocalIconSets([ + 'funding' => __DIR__ . '/resources/icons/funding', + 'podcasting' => __DIR__ . '/resources/icons/podcasting', + 'social' => __DIR__ . '/resources/icons/social', + 'custom' => __DIR__ . '/resources/icons/custom', + ]) + ->withDefaultIconPerSet([ + 'funding' => 'funding:default', + 'podcasting' => 'podcasting:default', + 'social' => 'social:default', + ]) + ->withDefaultPrefix('ri') + ->withPlaceholder('�'); diff --git a/phpstan.neon b/phpstan.neon index cbedb485..37bf9c76 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,23 +3,51 @@ parameters: level: 6 paths: - app + - modules - tests bootstrapFiles: - vendor/codeigniter4/framework/system/Test/bootstrap.php scanDirectories: - - app/Helpers - - modules/Analytics/Helpers - - modules/Fediverse/Helpers - - modules/PremiumPodcasts/Helpers - - vendor/codeigniter4/framework/system/Helpers - - vendor/myth/auth/src/Helpers + - app + - modules + - vendor/codeigniter4 excludePaths: - app/Libraries/Router.php - app/Views/* - modules/*/Views/* - themes/* + codeigniter: + additionalConfigNamespaces: + - Modules\Admin\Config\ + - Modules\Analytics\Config\ + - Modules\Api\Rest\V1\Config\ + - Modules\Auth\Config\ + - Modules\Fediverse\Config\ + - Modules\Install\Config\ + - Modules\Media\Config\ + - Modules\MediaClipper\Config\ + - Modules\Plugins\Config\ + - Modules\PodcastImport\Config\ + - Modules\PremiumPodcasts\Config\ + - Modules\WebSub\Config\ + - ViewThemes\Config\ + - Vite\Config\ + additionalModelNamespaces: + - Modules\Analytics\Models\ + - Modules\Auth\Models\ + - Modules\Fediverse\Models\ + - Modules\Media\Models\ + - Modules\PremiumPodcasts\Models\ + - CodeIgniter\Shield\Models\ + additionalServices: + - CodeIgniter\Settings\Config\Services + - CodeIgniter\Shield\Config\Services + - Michalsn\Uuid\Config\Services + - Modules\Media\Config\Services + - Modules\Platforms\Config\Services + - Modules\Plugins\Config\Services + - Modules\PremiumPodcasts\Config\Services + - Modules\Api\Rest\V1\Config\Services ignoreErrors: - - '#Cannot access property [\$a-z_]+ on ((array\|)?object)#' - - '#^Call to an undefined method CodeIgniter\\Database\\ConnectionInterface#' - - '#^Access to an undefined property App\\Entities\\Media\\Image#' - - '#^Call to an undefined method CodeIgniter\\HTTP\\RequestInterface#' + - identifier: missingType.generics + - '#\$callback of static method CodeIgniter\\Events\\Events\:\:on\(\) expects callable\(mixed\)\: mixed, Closure\(mixed, mixed\)\: void given.#' diff --git a/phpunit.xml.dist b/phpunit.xml.dist index dcd86923..766a18f5 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,56 +1,66 @@ - - - - - ./app - - - ./app/Views - ./app/Config/Routes.php - - - - - - - - - - - ./tests - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + ./tests + + + + + + + + + + ./app + + + ./app/Views + ./app/Config/Routes.php + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/.gitkeep b/plugins/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..6e2c7420 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,16208 @@ +lockfileVersion: "9.0" + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + .: + dependencies: + "@amcharts/amcharts4": + specifier: ^4.10.40 + version: 4.10.40 + "@amcharts/amcharts4-geodata": + specifier: ^4.1.31 + version: 4.1.31 + "@codemirror/commands": + specifier: ^6.10.2 + version: 6.10.2 + "@codemirror/lang-html": + specifier: ^6.4.11 + version: 6.4.11 + "@codemirror/lang-xml": + specifier: ^6.1.0 + version: 6.1.0 + "@codemirror/language": + specifier: ^6.12.1 + version: 6.12.1 + "@codemirror/state": + specifier: ^6.5.4 + version: 6.5.4 + "@codemirror/view": + specifier: ^6.39.14 + version: 6.39.14 + "@floating-ui/dom": + specifier: ^1.7.5 + version: 1.7.5 + "@github/clipboard-copy-element": + specifier: ^1.3.0 + version: 1.3.0 + "@github/hotkey": + specifier: ^3.1.1 + version: 3.1.1 + "@github/markdown-toolbar-element": + specifier: ^2.2.3 + version: 2.2.3 + "@github/relative-time-element": + specifier: ^5.0.0 + version: 5.0.0 + "@patternfly/elements": + specifier: ^4.3.1 + version: 4.3.1 + "@vime/core": + specifier: ^5.4.1 + version: 5.4.1 + choices.js: + specifier: ^11.1.0 + version: 11.1.0 + codemirror: + specifier: ^6.0.2 + version: 6.0.2 + flatpickr: + specifier: ^4.6.13 + version: 4.6.13 + htmlfy: + specifier: ^1.0.1 + version: 1.0.1 + leaflet: + specifier: ^1.9.4 + version: 1.9.4 + leaflet.markercluster: + specifier: ^1.5.3 + version: 1.5.3(leaflet@1.9.4) + lit: + specifier: ^3.3.2 + version: 3.3.2 + marked: + specifier: ^17.0.3 + version: 17.0.3 + wavesurfer.js: + specifier: ^7.12.1 + version: 7.12.1 + xml-formatter: + specifier: ^3.6.7 + version: 3.6.7 + devDependencies: + "@commitlint/cli": + specifier: ^20.4.2 + version: 20.4.2(@types/node@24.7.0)(typescript@5.9.3) + "@commitlint/config-conventional": + specifier: ^20.4.2 + version: 20.4.2 + "@csstools/css-tokenizer": + specifier: ^4.0.0 + version: 4.0.0 + "@eslint/eslintrc": + specifier: ^3.3.3 + version: 3.3.3 + "@eslint/js": + specifier: ^10.0.1 + version: 10.0.1(eslint@10.0.0(jiti@1.21.7)) + "@semantic-release/changelog": + specifier: ^6.0.3 + version: 6.0.3(semantic-release@25.0.3(typescript@5.9.3)) + "@semantic-release/exec": + specifier: ^7.1.0 + version: 7.1.0(semantic-release@25.0.3(typescript@5.9.3)) + "@semantic-release/git": + specifier: ^10.0.1 + version: 10.0.1(semantic-release@25.0.3(typescript@5.9.3)) + "@semantic-release/gitlab": + specifier: ^13.3.0 + version: 13.3.0(semantic-release@25.0.3(typescript@5.9.3)) + "@tailwindcss/forms": + specifier: ^0.5.11 + version: 0.5.11(tailwindcss@3.4.19(yaml@2.8.2)) + "@tailwindcss/typography": + specifier: ^0.5.19 + version: 0.5.19(tailwindcss@3.4.19(yaml@2.8.2)) + "@types/leaflet": + specifier: ^1.9.21 + version: 1.9.21 + all-contributors-cli: + specifier: ^6.26.1 + version: 6.26.1 + commitizen: + specifier: ^4.3.1 + version: 4.3.1(@types/node@24.7.0)(typescript@5.9.3) + conventional-changelog-conventionalcommits: + specifier: ^9.1.0 + version: 9.1.0 + cross-env: + specifier: ^10.1.0 + version: 10.1.0 + cssnano: + specifier: ^7.1.2 + version: 7.1.2(postcss@8.5.6) + cz-conventional-changelog: + specifier: ^3.3.0 + version: 3.3.0(@types/node@24.7.0)(typescript@5.9.3) + eslint: + specifier: ^10.0.0 + version: 10.0.0(jiti@1.21.7) + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@10.0.0(jiti@1.21.7)) + eslint-plugin-prettier: + specifier: ^5.5.5 + version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.0.0(jiti@1.21.7)))(eslint@10.0.0(jiti@1.21.7))(prettier@3.8.1) + glob: + specifier: ^13.0.5 + version: 13.0.5 + globals: + specifier: ^17.3.0 + version: 17.3.0 + husky: + specifier: ^9.1.7 + version: 9.1.7 + is-ci: + specifier: ^4.1.0 + version: 4.1.0 + lint-staged: + specifier: ^16.2.7 + version: 16.2.7 + postcss: + specifier: ^8.5.6 + version: 8.5.6 + postcss-import: + specifier: ^16.1.1 + version: 16.1.1(postcss@8.5.6) + postcss-nesting: + specifier: ^14.0.0 + version: 14.0.0(postcss@8.5.6) + postcss-preset-env: + specifier: ^11.1.3 + version: 11.1.3(postcss@8.5.6) + postcss-reporter: + specifier: ^7.1.0 + version: 7.1.0(postcss@8.5.6) + prettier: + specifier: 3.8.1 + version: 3.8.1 + prettier-plugin-organize-imports: + specifier: ^4.3.0 + version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) + semantic-release: + specifier: ^25.0.3 + version: 25.0.3(typescript@5.9.3) + sharp: + specifier: ^0.34.5 + version: 0.34.5 + stylelint: + specifier: ^17.3.0 + version: 17.3.0(typescript@5.9.3) + stylelint-config-standard: + specifier: ^40.0.0 + version: 40.0.0(stylelint@17.3.0(typescript@5.9.3)) + svgo: + specifier: ^4.0.0 + version: 4.0.0 + tailwindcss: + specifier: ^3.4.19 + version: 3.4.19(yaml@2.8.2) + typescript: + specifier: ~5.9.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.56.0 + version: 8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3) + vite: + specifier: ^7.3.1 + version: 7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2) + vite-plugin-codeigniter: + specifier: ^2.0.0 + version: 2.0.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)) + vite-plugin-inspect: + specifier: ^11.3.3 + version: 11.3.3(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)) + vite-plugin-pwa: + specifier: ^1.2.0 + version: 1.2.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.4.0)(workbox-window@7.4.0) + vite-plugin-static-copy: + specifier: ^3.2.0 + version: 3.2.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)) + workbox-build: + specifier: ^7.4.0 + version: 7.4.0 + workbox-core: + specifier: ^7.4.0 + version: 7.4.0 + workbox-routing: + specifier: ^7.4.0 + version: 7.4.0 + workbox-strategies: + specifier: ^7.4.0 + version: 7.4.0 + +packages: + "@actions/core@3.0.0": + resolution: + { + integrity: sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg==, + } + + "@actions/exec@3.0.0": + resolution: + { + integrity: sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==, + } + + "@actions/http-client@4.0.0": + resolution: + { + integrity: sha512-QuwPsgVMsD6qaPD57GLZi9sqzAZCtiJT8kVBCDpLtxhL5MydQ4gS+DrejtZZPdIYyB1e95uCK9Luyds7ybHI3g==, + } + + "@actions/io@3.0.2": + resolution: + { + integrity: sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==, + } + + "@alloc/quick-lru@5.2.0": + resolution: + { + integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==, + } + engines: { node: ">=10" } + + "@amcharts/amcharts4-geodata@4.1.31": + resolution: + { + integrity: sha512-ciX2rOX6YZaRv/GGXqQNjV4ixhe1Ke9r+4xlwm9YuUHZ5QZSfLx6obXRqhmQeTdnNcxjveMoZ//E7xfotvqgVg==, + } + + "@amcharts/amcharts4@4.10.40": + resolution: + { + integrity: sha512-F5RrlWCg/fIRvTnnXenWZg7bTlEWJDvELyvXVAAi5GFvFVF4IegIP1vk5TatkgBzYO5v+SNGj2S3N1MkLwYA8w==, + } + + "@apideck/better-ajv-errors@0.3.6": + resolution: + { + integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==, + } + engines: { node: ">=10" } + peerDependencies: + ajv: ">=8" + + "@babel/code-frame@7.29.0": + resolution: + { + integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==, + } + engines: { node: ">=6.9.0" } + + "@babel/compat-data@7.29.0": + resolution: + { + integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==, + } + engines: { node: ">=6.9.0" } + + "@babel/core@7.29.0": + resolution: + { + integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==, + } + engines: { node: ">=6.9.0" } + + "@babel/generator@7.29.1": + resolution: + { + integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-annotate-as-pure@7.27.3": + resolution: + { + integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-compilation-targets@7.28.6": + resolution: + { + integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-create-class-features-plugin@7.28.6": + resolution: + { + integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/helper-create-regexp-features-plugin@7.28.5": + resolution: + { + integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/helper-define-polyfill-provider@0.6.6": + resolution: + { + integrity: sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==, + } + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + + "@babel/helper-globals@7.28.0": + resolution: + { + integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-member-expression-to-functions@7.28.5": + resolution: + { + integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-module-imports@7.28.6": + resolution: + { + integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-module-transforms@7.28.6": + resolution: + { + integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/helper-optimise-call-expression@7.27.1": + resolution: + { + integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-plugin-utils@7.28.6": + resolution: + { + integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-remap-async-to-generator@7.27.1": + resolution: + { + integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/helper-replace-supers@7.28.6": + resolution: + { + integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/helper-skip-transparent-expression-wrappers@7.27.1": + resolution: + { + integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-string-parser@7.27.1": + resolution: + { + integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-validator-identifier@7.28.5": + resolution: + { + integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-validator-option@7.27.1": + resolution: + { + integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-wrap-function@7.28.6": + resolution: + { + integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==, + } + engines: { node: ">=6.9.0" } + + "@babel/helpers@7.28.6": + resolution: + { + integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==, + } + engines: { node: ">=6.9.0" } + + "@babel/parser@7.29.0": + resolution: + { + integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==, + } + engines: { node: ">=6.0.0" } + hasBin: true + + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5": + resolution: + { + integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1": + resolution: + { + integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1": + resolution: + { + integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1": + resolution: + { + integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.13.0 + + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6": + resolution: + { + integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": + resolution: + { + integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-import-assertions@7.28.6": + resolution: + { + integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-import-attributes@7.28.6": + resolution: + { + integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-unicode-sets-regex@7.18.6": + resolution: + { + integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/plugin-transform-arrow-functions@7.27.1": + resolution: + { + integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-async-generator-functions@7.29.0": + resolution: + { + integrity: sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-async-to-generator@7.28.6": + resolution: + { + integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-block-scoped-functions@7.27.1": + resolution: + { + integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-block-scoping@7.28.6": + resolution: + { + integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-class-properties@7.28.6": + resolution: + { + integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-class-static-block@7.28.6": + resolution: + { + integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.12.0 + + "@babel/plugin-transform-classes@7.28.6": + resolution: + { + integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-computed-properties@7.28.6": + resolution: + { + integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-destructuring@7.28.5": + resolution: + { + integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-dotall-regex@7.28.6": + resolution: + { + integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-duplicate-keys@7.27.1": + resolution: + { + integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0": + resolution: + { + integrity: sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/plugin-transform-dynamic-import@7.27.1": + resolution: + { + integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-explicit-resource-management@7.28.6": + resolution: + { + integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-exponentiation-operator@7.28.6": + resolution: + { + integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-export-namespace-from@7.27.1": + resolution: + { + integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-for-of@7.27.1": + resolution: + { + integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-function-name@7.27.1": + resolution: + { + integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-json-strings@7.28.6": + resolution: + { + integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-literals@7.27.1": + resolution: + { + integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-logical-assignment-operators@7.28.6": + resolution: + { + integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-member-expression-literals@7.27.1": + resolution: + { + integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-modules-amd@7.27.1": + resolution: + { + integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-modules-commonjs@7.28.6": + resolution: + { + integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-modules-systemjs@7.29.0": + resolution: + { + integrity: sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-modules-umd@7.27.1": + resolution: + { + integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-named-capturing-groups-regex@7.29.0": + resolution: + { + integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/plugin-transform-new-target@7.27.1": + resolution: + { + integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-nullish-coalescing-operator@7.28.6": + resolution: + { + integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-numeric-separator@7.28.6": + resolution: + { + integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-object-rest-spread@7.28.6": + resolution: + { + integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-object-super@7.27.1": + resolution: + { + integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-optional-catch-binding@7.28.6": + resolution: + { + integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-optional-chaining@7.28.6": + resolution: + { + integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-parameters@7.27.7": + resolution: + { + integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-private-methods@7.28.6": + resolution: + { + integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-private-property-in-object@7.28.6": + resolution: + { + integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-property-literals@7.27.1": + resolution: + { + integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-regenerator@7.29.0": + resolution: + { + integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-regexp-modifiers@7.28.6": + resolution: + { + integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/plugin-transform-reserved-words@7.27.1": + resolution: + { + integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-shorthand-properties@7.27.1": + resolution: + { + integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-spread@7.28.6": + resolution: + { + integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-sticky-regex@7.27.1": + resolution: + { + integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-template-literals@7.27.1": + resolution: + { + integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-typeof-symbol@7.27.1": + resolution: + { + integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-unicode-escapes@7.27.1": + resolution: + { + integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-unicode-property-regex@7.28.6": + resolution: + { + integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-unicode-regex@7.27.1": + resolution: + { + integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-unicode-sets-regex@7.28.6": + resolution: + { + integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/preset-env@7.29.0": + resolution: + { + integrity: sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/preset-modules@0.1.6-no-external-plugins": + resolution: + { + integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==, + } + peerDependencies: + "@babel/core": ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + "@babel/runtime@7.28.6": + resolution: + { + integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==, + } + engines: { node: ">=6.9.0" } + + "@babel/template@7.28.6": + resolution: + { + integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==, + } + engines: { node: ">=6.9.0" } + + "@babel/traverse@7.29.0": + resolution: + { + integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==, + } + engines: { node: ">=6.9.0" } + + "@babel/types@7.29.0": + resolution: + { + integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==, + } + engines: { node: ">=6.9.0" } + + "@cacheable/memory@2.0.7": + resolution: + { + integrity: sha512-RbxnxAMf89Tp1dLhXMS7ceft/PGsDl1Ip7T20z5nZ+pwIAsQ1p2izPjVG69oCLv/jfQ7HDPHTWK0c9rcAWXN3A==, + } + + "@cacheable/utils@2.3.4": + resolution: + { + integrity: sha512-knwKUJEYgIfwShABS1BX6JyJJTglAFcEU7EXqzTdiGCXur4voqkiJkdgZIQtWNFhynzDWERcTYv/sETMu3uJWA==, + } + + "@codemirror/autocomplete@6.20.0": + resolution: + { + integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==, + } + + "@codemirror/commands@6.10.2": + resolution: + { + integrity: sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==, + } + + "@codemirror/lang-css@6.3.1": + resolution: + { + integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==, + } + + "@codemirror/lang-html@6.4.11": + resolution: + { + integrity: sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==, + } + + "@codemirror/lang-javascript@6.2.4": + resolution: + { + integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==, + } + + "@codemirror/lang-xml@6.1.0": + resolution: + { + integrity: sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==, + } + + "@codemirror/language@6.12.1": + resolution: + { + integrity: sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==, + } + + "@codemirror/lint@6.9.4": + resolution: + { + integrity: sha512-ABc9vJ8DEmvOWuH26P3i8FpMWPQkduD9Rvba5iwb6O3hxASgclm3T3krGo8NASXkHCidz6b++LWlzWIUfEPSWw==, + } + + "@codemirror/search@6.6.0": + resolution: + { + integrity: sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==, + } + + "@codemirror/state@6.5.4": + resolution: + { + integrity: sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==, + } + + "@codemirror/view@6.39.14": + resolution: + { + integrity: sha512-WJcvgHm/6Q7dvGT0YFv/6PSkoc36QlR0VCESS6x9tGsnF1lWLmmYxOgX3HH6v8fo6AvSLgpcs+H0Olre6MKXlg==, + } + + "@colors/colors@1.5.0": + resolution: + { + integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==, + } + engines: { node: ">=0.1.90" } + + "@commitlint/cli@20.4.2": + resolution: + { + integrity: sha512-YjYSX2yj/WsVoxh9mNiymfFS2ADbg2EK4+1WAsMuckwKMCqJ5PDG0CJU/8GvmHWcv4VRB2V02KqSiecRksWqZQ==, + } + engines: { node: ">=v18" } + hasBin: true + + "@commitlint/config-conventional@20.4.2": + resolution: + { + integrity: sha512-rwkTF55q7Q+6dpSKUmJoScV0f3EpDlWKw2UPzklkLS4o5krMN1tPWAVOgHRtyUTMneIapLeQwaCjn44Td6OzBQ==, + } + engines: { node: ">=v18" } + + "@commitlint/config-validator@20.4.0": + resolution: + { + integrity: sha512-zShmKTF+sqyNOfAE0vKcqnpvVpG0YX8F9G/ZIQHI2CoKyK+PSdladXMSns400aZ5/QZs+0fN75B//3Q5CHw++w==, + } + engines: { node: ">=v18" } + + "@commitlint/ensure@20.4.1": + resolution: + { + integrity: sha512-WLQqaFx1pBooiVvBrA1YfJNFqZF8wS/YGOtr5RzApDbV9tQ52qT5VkTsY65hFTnXhW8PcDfZLaknfJTmPejmlw==, + } + engines: { node: ">=v18" } + + "@commitlint/execute-rule@20.0.0": + resolution: + { + integrity: sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==, + } + engines: { node: ">=v18" } + + "@commitlint/format@20.4.0": + resolution: + { + integrity: sha512-i3ki3WR0rgolFVX6r64poBHXM1t8qlFel1G1eCBvVgntE3fCJitmzSvH5JD/KVJN/snz6TfaX2CLdON7+s4WVQ==, + } + engines: { node: ">=v18" } + + "@commitlint/is-ignored@20.4.1": + resolution: + { + integrity: sha512-In5EO4JR1lNsAv1oOBBO24V9ND1IqdAJDKZiEpdfjDl2HMasAcT7oA+5BKONv1pRoLG380DGPE2W2RIcUwdgLA==, + } + engines: { node: ">=v18" } + + "@commitlint/lint@20.4.2": + resolution: + { + integrity: sha512-buquzNRtFng6xjXvBU1abY/WPEEjCgUipNQrNmIWe8QuJ6LWLtei/LDBAzEe5ASm45+Q9L2Xi3/GVvlj50GAug==, + } + engines: { node: ">=v18" } + + "@commitlint/load@20.4.0": + resolution: + { + integrity: sha512-Dauup/GfjwffBXRJUdlX/YRKfSVXsXZLnINXKz0VZkXdKDcaEILAi9oflHGbfydonJnJAbXEbF3nXPm9rm3G6A==, + } + engines: { node: ">=v18" } + + "@commitlint/message@20.4.0": + resolution: + { + integrity: sha512-B5lGtvHgiLAIsK5nLINzVW0bN5hXv+EW35sKhYHE8F7V9Uz1fR4tx3wt7mobA5UNhZKUNgB/+ldVMQE6IHZRyA==, + } + engines: { node: ">=v18" } + + "@commitlint/parse@20.4.1": + resolution: + { + integrity: sha512-XNtZjeRcFuAfUnhYrCY02+mpxwY4OmnvD3ETbVPs25xJFFz1nRo/25nHj+5eM+zTeRFvWFwD4GXWU2JEtoK1/w==, + } + engines: { node: ">=v18" } + + "@commitlint/read@20.4.0": + resolution: + { + integrity: sha512-QfpFn6/I240ySEGv7YWqho4vxqtPpx40FS7kZZDjUJ+eHxu3azfhy7fFb5XzfTqVNp1hNoI3tEmiEPbDB44+cg==, + } + engines: { node: ">=v18" } + + "@commitlint/resolve-extends@20.4.0": + resolution: + { + integrity: sha512-ay1KM8q0t+/OnlpqXJ+7gEFQNlUtSU5Gxr8GEwnVf2TPN3+ywc5DzL3JCxmpucqxfHBTFwfRMXxPRRnR5Ki20g==, + } + engines: { node: ">=v18" } + + "@commitlint/rules@20.4.2": + resolution: + { + integrity: sha512-oz83pnp5Yq6uwwTAabuVQPNlPfeD2Y5ZjMb7Wx8FSUlu4sLYJjbBWt8031Z0osCFPfHzAwSYrjnfDFKtuSMdKg==, + } + engines: { node: ">=v18" } + + "@commitlint/to-lines@20.0.0": + resolution: + { + integrity: sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw==, + } + engines: { node: ">=v18" } + + "@commitlint/top-level@20.4.0": + resolution: + { + integrity: sha512-NDzq8Q6jmFaIIBC/GG6n1OQEaHdmaAAYdrZRlMgW6glYWGZ+IeuXmiymDvQNXPc82mVxq2KiE3RVpcs+1OeDeA==, + } + engines: { node: ">=v18" } + + "@commitlint/types@20.4.0": + resolution: + { + integrity: sha512-aO5l99BQJ0X34ft8b0h7QFkQlqxC6e7ZPVmBKz13xM9O8obDaM1Cld4sQlJDXXU/VFuUzQ30mVtHjVz74TuStw==, + } + engines: { node: ">=v18" } + + "@csstools/cascade-layer-name-parser@3.0.0": + resolution: + { + integrity: sha512-/3iksyevwRfSJx5yH0RkcrcYXwuhMQx3Juqf40t97PeEy2/Mz2TItZ/z/216qpe4GgOyFBP8MKIwVvytzHmfIQ==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + "@csstools/css-parser-algorithms": ^4.0.0 + "@csstools/css-tokenizer": ^4.0.0 + + "@csstools/color-helpers@6.0.1": + resolution: + { + integrity: sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==, + } + engines: { node: ">=20.19.0" } + + "@csstools/css-calc@3.1.1": + resolution: + { + integrity: sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + "@csstools/css-parser-algorithms": ^4.0.0 + "@csstools/css-tokenizer": ^4.0.0 + + "@csstools/css-color-parser@4.0.1": + resolution: + { + integrity: sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + "@csstools/css-parser-algorithms": ^4.0.0 + "@csstools/css-tokenizer": ^4.0.0 + + "@csstools/css-parser-algorithms@4.0.0": + resolution: + { + integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + "@csstools/css-tokenizer": ^4.0.0 + + "@csstools/css-syntax-patches-for-csstree@1.0.27": + resolution: + { + integrity: sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==, + } + + "@csstools/css-tokenizer@4.0.0": + resolution: + { + integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==, + } + engines: { node: ">=20.19.0" } + + "@csstools/media-query-list-parser@5.0.0": + resolution: + { + integrity: sha512-T9lXmZOfnam3eMERPsszjY5NK0jX8RmThmmm99FZ8b7z8yMaFZWKwLWGZuTwdO3ddRY5fy13GmmEYZXB4I98Eg==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + "@csstools/css-parser-algorithms": ^4.0.0 + "@csstools/css-tokenizer": ^4.0.0 + + "@csstools/postcss-alpha-function@2.0.2": + resolution: + { + integrity: sha512-EXdJC5fds0h1KqoioUBkcYPZvcNKR64jrGkbqlDNbMU3FP1MzLEr/QJR8bj/bu53TJFIgkc9WvKcpbwVqZ4WPg==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-cascade-layers@6.0.0": + resolution: + { + integrity: sha512-WhsECqmrEZQGqaPlBA7JkmF/CJ2/+wetL4fkL9sOPccKd32PQ1qToFM6gqSI5rkpmYqubvbxjEJhyMTHYK0vZQ==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-color-function-display-p3-linear@2.0.1": + resolution: + { + integrity: sha512-blnzzMkMswoagp1u3JS1OiiTuQCW1F+lQEtlxu2BXhTUmEeKHhSgrrAceF7s4bwZOwKYbkxuw/FC9Ni/zxB7Xw==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-color-function@5.0.1": + resolution: + { + integrity: sha512-SNU4o63+oZpB7ufkTmj3FholvMtJwuyIWqTOVOxnZjNDFEg1hwdbnPjoytZVgKRQGkvkHdAS0uZWn0zH+ZwXCQ==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-color-mix-function@4.0.1": + resolution: + { + integrity: sha512-B9XBCd8cmHVwnc5YTn2YVXOlNMTNwuPIpJQ87665vaNdfNorVWz8JhAAv7Vq0v66TA6htE7+QW0OidL/QV0tiA==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-color-mix-variadic-function-arguments@2.0.1": + resolution: + { + integrity: sha512-PV5nv9EHsEsvC5GlVqAHa1PznP/qZxFAIABImrkGJUbSoFUTwpnPch/dYSKw52CQ0aNnwCqMHoM29wDwmpVLqw==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-content-alt-text@3.0.0": + resolution: + { + integrity: sha512-OHa+4aCcrJtHpPWB3zptScHwpS1TUbeLR4uO0ntIz0Su/zw9SoWkVu+tDMSySSAsNtNSI3kut4fTliFwIsrHxA==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-contrast-color-function@3.0.1": + resolution: + { + integrity: sha512-Zy2gyAPsUyoAUkmBjLbWcXJhglM+toBRpNegyJc/LTHpSpIbMKVmByGQ+VSw01E1Pov8Dk/fgEs9hd11xtGC8g==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-exponential-functions@3.0.0": + resolution: + { + integrity: sha512-KCtnlZw1VrDCAbYxE44rUHONYAkjhh0/iS5T3L2K5OHuvoSEvxDjJO82pRwTmsRxVtSiC+syPjx2k2xsqHOM7w==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-font-format-keywords@5.0.0": + resolution: + { + integrity: sha512-M1EjCe/J3u8fFhOZgRci74cQhJ7R0UFBX6T+WqoEvjrr8hVfMiV+HTYrzxLY5OW8YllvXYr5Q5t5OvJbsUSeDg==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-gamut-mapping@3.0.1": + resolution: + { + integrity: sha512-0S7D+gArVXsgRDxjoNv8g2QlaIi/SegqdlTMgVwowaPSyxaZsVnwrhShvmlpoLOVHmpJfHKGiXzn1Hc1BcZCzQ==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-gradients-interpolation-method@6.0.1": + resolution: + { + integrity: sha512-Y5dxOstuUCdmU1tuEB/EgKxDw+/DAZes4gQeitb/H0S5khmjT24CfbVa/l2ZelNCEEq9KjxqO2cjwDV2vqj62w==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-hwb-function@5.0.1": + resolution: + { + integrity: sha512-9f8TA/B8iEpzF0y4Z6qPVfP9nMp2ti10OFbtyDtoBz3+eK0KPV4CCCjTwYIpPRopLgctFZt7xqmOxA7JgAJEug==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-ic-unit@5.0.0": + resolution: + { + integrity: sha512-/ws5d6c4uKqfM9zIL3ugcGI+3fvZEOOkJHNzAyTAGJIdZ+aSL9BVPNlHGV4QzmL0vqBSCOdU3+rhcMEj3+KzYw==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-initial@3.0.0": + resolution: + { + integrity: sha512-UVUrFmrTQyLomVepnjWlbBg7GoscLmXLwYFyjbcEnmpeGW7wde6lNpx5eM3eVwZI2M+7hCE3ykYnAsEPLcLa+Q==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-is-pseudo-class@6.0.0": + resolution: + { + integrity: sha512-1Hdy/ykg9RDo8vU8RiM2o+RaXO39WpFPaIkHxlAEJFofle/lc33tdQMKhBk3jR/Fe+uZNLOs3HlowFafyFptVw==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-light-dark-function@3.0.0": + resolution: + { + integrity: sha512-s++V5/hYazeRUCYIn2lsBVzUsxdeC46gtwpgW6lu5U/GlPOS5UTDT14kkEyPgXmFbCvaWLREqV7YTMJq1K3G6w==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-logical-float-and-clear@4.0.0": + resolution: + { + integrity: sha512-NGzdIRVj/VxOa/TjVdkHeyiJoDihONV0+uB0csUdgWbFFr8xndtfqK8iIGP9IKJzco+w0hvBF2SSk2sDSTAnOQ==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-logical-overflow@3.0.0": + resolution: + { + integrity: sha512-5cRg93QXVskM0MNepHpPcL0WLSf5Hncky0DrFDQY/4ozbH5lH7SX5ejayVpNTGSX7IpOvu7ykQDLOdMMGYzwpA==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-logical-overscroll-behavior@3.0.0": + resolution: + { + integrity: sha512-82Jnl/5Wi5jb19nQE1XlBHrZcNL3PzOgcj268cDkfwf+xi10HBqufGo1Unwf5n8bbbEFhEKgyQW+vFsc9iY1jw==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-logical-resize@4.0.0": + resolution: + { + integrity: sha512-L0T3q0gei/tGetCGZU0c7VN77VTivRpz1YZRNxjXYmW+85PKeI6U9YnSvDqLU2vBT2uN4kLEzfgZ0ThIZpN18A==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-logical-viewport-units@4.0.0": + resolution: + { + integrity: sha512-TA3AqVN/1IH3dKRC2UUWvprvwyOs2IeD7FDZk5Hz20w4q33yIuSg0i0gjyTUkcn90g8A4n7QpyZ2AgBrnYPnnA==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-media-minmax@3.0.0": + resolution: + { + integrity: sha512-42szvyZ/oqG7NSvBQOGq1IaJaHR6mr/iXqqjW8/JuIajIHRs9HcJR5ExC4vbyCqk+fr7/DIOhm5ZrELBytLDsw==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-media-queries-aspect-ratio-number-values@4.0.0": + resolution: + { + integrity: sha512-FDdC3lbrj8Vr0SkGIcSLTcRB7ApG6nlJFxOxkEF2C5hIZC1jtgjISFSGn/WjFdVkn8Dqe+Vx9QXI3axS2w1XHw==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-mixins@1.0.0": + resolution: + { + integrity: sha512-rz6qjT2w9L3k65jGc2dX+3oGiSrYQ70EZPDrINSmSVoVys7lLBFH0tvEa8DW2sr9cbRVD/W+1sy8+7bfu0JUfg==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-nested-calc@5.0.0": + resolution: + { + integrity: sha512-aPSw8P60e/i9BEfugauhikBqgjiwXcw3I9o4vXs+hktl4NSTgZRI0QHimxk9mst8N01A2TKDBxOln3mssRxiHQ==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-normalize-display-values@5.0.1": + resolution: + { + integrity: sha512-FcbEmoxDEGYvm2W3rQzVzcuo66+dDJjzzVDs+QwRmZLHYofGmMGwIKPqzF86/YW+euMDa7sh1xjWDvz/fzByZQ==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-oklab-function@5.0.1": + resolution: + { + integrity: sha512-Ql+X4zu29ITihxHKcCFEU84ww+Nkv44M2s0fT7Nv4iQYlQ4+liF6v9RL0ezeogeiLRNLLC6yh0ay1PHpmaNIgQ==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-position-area-property@2.0.0": + resolution: + { + integrity: sha512-TeEfzsJGB23Syv7yCm8AHCD2XTFujdjr9YYu9ebH64vnfCEvY4BG319jXAYSlNlf3Yc9PNJ6WnkDkUF5XVgSKQ==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-progressive-custom-properties@5.0.0": + resolution: + { + integrity: sha512-NsJoZ89rxmDrUsITf8QIk5w+lQZQ8Xw5K6cLFG+cfiffsLYHb3zcbOOrHLetGl1WIhjWWQ4Cr8MMrg46Q+oACg==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-property-rule-prelude-list@2.0.0": + resolution: + { + integrity: sha512-qcMAkc9AhpzHgmQCD8hoJgGYifcOAxd1exXjjxilMM6euwRE619xDa4UsKBCv/v4g+sS63sd6c29LPM8s2ylSQ==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-random-function@3.0.0": + resolution: + { + integrity: sha512-H/Zt5o9NAd8mowq3XRy8uU19wOEe8sbKyKOKxrzOdG0rz2maA4fLcXc9MQucdm3s4zMDfVJtCqvwrLP7lKWybA==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-relative-color-syntax@4.0.1": + resolution: + { + integrity: sha512-zRLO9xMGtCCT0FTpTsaGI6cmdzJKbwWjg92AuczlSDuriEAPEJL+ZJ4jDyw51p23DfoAFgK8soB/LyoY1kFOLQ==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-scope-pseudo-class@5.0.0": + resolution: + { + integrity: sha512-kBrBFJcAji3MSHS4qQIihPvJfJC5xCabXLbejqDMiQi+86HD4eMBiTayAo46Urg7tlEmZZQFymFiJt+GH6nvXw==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-sign-functions@2.0.0": + resolution: + { + integrity: sha512-32Bw7++8ToSLMEOSJUuxJsAJJdsIfgeD1dYPKRCk9/fTciVZ8MjkPXypwiXIo7xIJk0h5CJz6QUkDoc6dcAJ7Q==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-stepped-value-functions@5.0.0": + resolution: + { + integrity: sha512-NueCSNbaq7QtAj6QwseMqOlM3C8nN2GWaPwd2Uw+IOYAbGvO/84BxUtNeZljeOmqJX61hwSNhLfwmgJXgY0W5A==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-syntax-descriptor-syntax-production@2.0.0": + resolution: + { + integrity: sha512-elYcbdiBXAkPqvojB9kIBRuHY6htUhjSITtFQ+XiXnt6SvZCbNGxQmaaw6uZ7SPHu/+i/XVjzIt09/1k3SIerQ==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-system-ui-font-family@2.0.0": + resolution: + { + integrity: sha512-FyGZCgchFImFyiHS2x3rD5trAqatf/x23veBLTIgbaqyFfna6RNBD+Qf8HRSjt6HGMXOLhAjxJ3OoZg0bbn7Qw==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-text-decoration-shorthand@5.0.2": + resolution: + { + integrity: sha512-0VUTt79lfQ2LGQLfyOBeqpinDLzOf3w+tlA1Re/KjSOc86H6tRz6TeXbISrBSJlfM1fYKNmBNw+ON8Ovy6aNeg==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-trigonometric-functions@5.0.0": + resolution: + { + integrity: sha512-isjkD3l1MVjanGuaS7RIYP/9txZKbZ8eQPaUHoxEWmySm3k6KutSepzPINL6MXyyi0ZUijZcktA++/L66IK71A==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/postcss-unset-value@5.0.0": + resolution: + { + integrity: sha512-EoO54sS2KCIfesvHyFYAW99RtzwHdgaJzhl7cqKZSaMYKZv3fXSOehDjAQx8WZBKn1JrMd7xJJI1T1BxPF7/jA==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@csstools/selector-resolve-nested@4.0.0": + resolution: + { + integrity: sha512-9vAPxmp+Dx3wQBIUwc1v7Mdisw1kbbaGqXUM8QLTgWg7SoPGYtXBsMXvsFs/0Bn5yoFhcktzxNZGNaUt0VjgjA==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss-selector-parser: ^7.1.1 + + "@csstools/selector-specificity@6.0.0": + resolution: + { + integrity: sha512-4sSgl78OtOXEX/2d++8A83zHNTgwCJMaR24FvsYL7Uf/VS8HZk9PTwR51elTbGqMuwH3szLvvOXEaVnqn0Z3zA==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss-selector-parser: ^7.1.1 + + "@csstools/utilities@3.0.0": + resolution: + { + integrity: sha512-etDqA/4jYvOGBM6yfKCOsEXfH96BKztZdgGmGqKi2xHnDe0ILIBraRspwgYatJH9JsCZ5HCGoCst8w18EKOAdg==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + "@emnapi/runtime@1.8.1": + resolution: + { + integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==, + } + + "@epic-web/invariant@1.0.0": + resolution: + { + integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==, + } + + "@esbuild/aix-ppc64@0.27.3": + resolution: + { + integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [aix] + + "@esbuild/android-arm64@0.27.3": + resolution: + { + integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [android] + + "@esbuild/android-arm@0.27.3": + resolution: + { + integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [android] + + "@esbuild/android-x64@0.27.3": + resolution: + { + integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [android] + + "@esbuild/darwin-arm64@0.27.3": + resolution: + { + integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [darwin] + + "@esbuild/darwin-x64@0.27.3": + resolution: + { + integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [darwin] + + "@esbuild/freebsd-arm64@0.27.3": + resolution: + { + integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [freebsd] + + "@esbuild/freebsd-x64@0.27.3": + resolution: + { + integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [freebsd] + + "@esbuild/linux-arm64@0.27.3": + resolution: + { + integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [linux] + + "@esbuild/linux-arm@0.27.3": + resolution: + { + integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [linux] + + "@esbuild/linux-ia32@0.27.3": + resolution: + { + integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [linux] + + "@esbuild/linux-loong64@0.27.3": + resolution: + { + integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==, + } + engines: { node: ">=18" } + cpu: [loong64] + os: [linux] + + "@esbuild/linux-mips64el@0.27.3": + resolution: + { + integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==, + } + engines: { node: ">=18" } + cpu: [mips64el] + os: [linux] + + "@esbuild/linux-ppc64@0.27.3": + resolution: + { + integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [linux] + + "@esbuild/linux-riscv64@0.27.3": + resolution: + { + integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==, + } + engines: { node: ">=18" } + cpu: [riscv64] + os: [linux] + + "@esbuild/linux-s390x@0.27.3": + resolution: + { + integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==, + } + engines: { node: ">=18" } + cpu: [s390x] + os: [linux] + + "@esbuild/linux-x64@0.27.3": + resolution: + { + integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [linux] + + "@esbuild/netbsd-arm64@0.27.3": + resolution: + { + integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [netbsd] + + "@esbuild/netbsd-x64@0.27.3": + resolution: + { + integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [netbsd] + + "@esbuild/openbsd-arm64@0.27.3": + resolution: + { + integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [openbsd] + + "@esbuild/openbsd-x64@0.27.3": + resolution: + { + integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [openbsd] + + "@esbuild/openharmony-arm64@0.27.3": + resolution: + { + integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [openharmony] + + "@esbuild/sunos-x64@0.27.3": + resolution: + { + integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [sunos] + + "@esbuild/win32-arm64@0.27.3": + resolution: + { + integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [win32] + + "@esbuild/win32-ia32@0.27.3": + resolution: + { + integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [win32] + + "@esbuild/win32-x64@0.27.3": + resolution: + { + integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [win32] + + "@eslint-community/eslint-utils@4.9.1": + resolution: + { + integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + "@eslint-community/regexpp@4.12.2": + resolution: + { + integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==, + } + engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + + "@eslint/config-array@0.23.1": + resolution: + { + integrity: sha512-uVSdg/V4dfQmTjJzR0szNczjOH/J+FyUMMjYtr07xFRXR7EDf9i1qdxrD0VusZH9knj1/ecxzCQQxyic5NzAiA==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + "@eslint/config-helpers@0.5.2": + resolution: + { + integrity: sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + "@eslint/core@1.1.0": + resolution: + { + integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + "@eslint/eslintrc@3.3.3": + resolution: + { + integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/js@10.0.1": + resolution: + { + integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + "@eslint/object-schema@3.0.1": + resolution: + { + integrity: sha512-P9cq2dpr+LU8j3qbLygLcSZrl2/ds/pUpfnHNNuk5HW7mnngHs+6WSq5C9mO3rqRX8A1poxqLTC9cu0KOyJlBg==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + "@eslint/plugin-kit@0.6.0": + resolution: + { + integrity: sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + "@floating-ui/core@1.7.4": + resolution: + { + integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==, + } + + "@floating-ui/dom@1.7.5": + resolution: + { + integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==, + } + + "@floating-ui/utils@0.2.10": + resolution: + { + integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==, + } + + "@foliojs-fork/fontkit@1.9.2": + resolution: + { + integrity: sha512-IfB5EiIb+GZk+77TRB86AHroVaqfq8JRFlUbz0WEwsInyCG0epX2tCPOy+UfaWPju30DeVoUAXfzWXmhn753KA==, + } + + "@foliojs-fork/linebreak@1.1.2": + resolution: + { + integrity: sha512-ZPohpxxbuKNE0l/5iBJnOAfUaMACwvUIKCvqtWGKIMv1lPYoNjYXRfhi9FeeV9McBkBLxsMFWTVVhHJA8cyzvg==, + } + + "@foliojs-fork/pdfkit@0.15.3": + resolution: + { + integrity: sha512-Obc0Wmy3bm7BINFVvPhcl2rnSSK61DQrlHU8aXnAqDk9LCjWdUOPwhgD8Ywz5VtuFjRxmVOM/kQ/XLIBjDvltw==, + } + + "@foliojs-fork/restructure@2.0.2": + resolution: + { + integrity: sha512-59SgoZ3EXbkfSX7b63tsou/SDGzwUEK6MuB5sKqgVK1/XE0fxmpsOb9DQI8LXW3KfGnAjImCGhhEb7uPPAUVNA==, + } + + "@github/clipboard-copy-element@1.3.0": + resolution: + { + integrity: sha512-wyntkQkwoLbLo+Hqg2LIVMXDIzcvUb9bSDz+clX6nVJItwzh103rHxdXFRZD+DmxVbuEW5xSznYQXkz1jZT+xg==, + } + + "@github/hotkey@3.1.1": + resolution: + { + integrity: sha512-H30I6XDO3gFSgLuEuHoMBRZG9c3uCKNdAcYklL1FaZDPdU1bXfgjnpzGDPcUr0U6eGQ+T3XLY9slatwZYWL1dA==, + } + + "@github/markdown-toolbar-element@2.2.3": + resolution: + { + integrity: sha512-AlquKGee+IWiAMYVB0xyHFZRMnu4n3X4HTvJHu79GiVJ1ojTukCWyxMlF5NMsecoLcBKsuBhx3QPv2vkE/zQ0A==, + } + + "@github/relative-time-element@5.0.0": + resolution: + { + integrity: sha512-L/2r0DNR/rMbmHWcsdmhtOiy2gESoGOhItNFD4zJ3nZfHl79Dx3N18Vfx/pYr2lruMOdk1cJZb4wEumm+Dxm1w==, + } + + "@humanfs/core@0.19.1": + resolution: + { + integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==, + } + engines: { node: ">=18.18.0" } + + "@humanfs/node@0.16.7": + resolution: + { + integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==, + } + engines: { node: ">=18.18.0" } + + "@humanwhocodes/module-importer@1.0.1": + resolution: + { + integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, + } + engines: { node: ">=12.22" } + + "@humanwhocodes/retry@0.4.3": + resolution: + { + integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==, + } + engines: { node: ">=18.18" } + + "@img/colour@1.0.0": + resolution: + { + integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==, + } + engines: { node: ">=18" } + + "@img/sharp-darwin-arm64@0.34.5": + resolution: + { + integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [darwin] + + "@img/sharp-darwin-x64@0.34.5": + resolution: + { + integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [darwin] + + "@img/sharp-libvips-darwin-arm64@1.2.4": + resolution: + { + integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==, + } + cpu: [arm64] + os: [darwin] + + "@img/sharp-libvips-darwin-x64@1.2.4": + resolution: + { + integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==, + } + cpu: [x64] + os: [darwin] + + "@img/sharp-libvips-linux-arm64@1.2.4": + resolution: + { + integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==, + } + cpu: [arm64] + os: [linux] + libc: [glibc] + + "@img/sharp-libvips-linux-arm@1.2.4": + resolution: + { + integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==, + } + cpu: [arm] + os: [linux] + libc: [glibc] + + "@img/sharp-libvips-linux-ppc64@1.2.4": + resolution: + { + integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==, + } + cpu: [ppc64] + os: [linux] + libc: [glibc] + + "@img/sharp-libvips-linux-riscv64@1.2.4": + resolution: + { + integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==, + } + cpu: [riscv64] + os: [linux] + libc: [glibc] + + "@img/sharp-libvips-linux-s390x@1.2.4": + resolution: + { + integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==, + } + cpu: [s390x] + os: [linux] + libc: [glibc] + + "@img/sharp-libvips-linux-x64@1.2.4": + resolution: + { + integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==, + } + cpu: [x64] + os: [linux] + libc: [glibc] + + "@img/sharp-libvips-linuxmusl-arm64@1.2.4": + resolution: + { + integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==, + } + cpu: [arm64] + os: [linux] + libc: [musl] + + "@img/sharp-libvips-linuxmusl-x64@1.2.4": + resolution: + { + integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==, + } + cpu: [x64] + os: [linux] + libc: [musl] + + "@img/sharp-linux-arm64@0.34.5": + resolution: + { + integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [linux] + libc: [glibc] + + "@img/sharp-linux-arm@0.34.5": + resolution: + { + integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm] + os: [linux] + libc: [glibc] + + "@img/sharp-linux-ppc64@0.34.5": + resolution: + { + integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [ppc64] + os: [linux] + libc: [glibc] + + "@img/sharp-linux-riscv64@0.34.5": + resolution: + { + integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [riscv64] + os: [linux] + libc: [glibc] + + "@img/sharp-linux-s390x@0.34.5": + resolution: + { + integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [s390x] + os: [linux] + libc: [glibc] + + "@img/sharp-linux-x64@0.34.5": + resolution: + { + integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [linux] + libc: [glibc] + + "@img/sharp-linuxmusl-arm64@0.34.5": + resolution: + { + integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [linux] + libc: [musl] + + "@img/sharp-linuxmusl-x64@0.34.5": + resolution: + { + integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [linux] + libc: [musl] + + "@img/sharp-wasm32@0.34.5": + resolution: + { + integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [wasm32] + + "@img/sharp-win32-arm64@0.34.5": + resolution: + { + integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [win32] + + "@img/sharp-win32-ia32@0.34.5": + resolution: + { + integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [ia32] + os: [win32] + + "@img/sharp-win32-x64@0.34.5": + resolution: + { + integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [win32] + + "@isaacs/cliui@9.0.0": + resolution: + { + integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==, + } + engines: { node: ">=18" } + + "@jridgewell/gen-mapping@0.3.13": + resolution: + { + integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==, + } + + "@jridgewell/remapping@2.3.5": + resolution: + { + integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==, + } + + "@jridgewell/resolve-uri@3.1.2": + resolution: + { + integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, + } + engines: { node: ">=6.0.0" } + + "@jridgewell/source-map@0.3.11": + resolution: + { + integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==, + } + + "@jridgewell/sourcemap-codec@1.5.5": + resolution: + { + integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==, + } + + "@jridgewell/trace-mapping@0.3.31": + resolution: + { + integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==, + } + + "@keyv/bigmap@1.3.1": + resolution: + { + integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==, + } + engines: { node: ">= 18" } + peerDependencies: + keyv: ^5.6.0 + + "@keyv/serialize@1.1.1": + resolution: + { + integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==, + } + + "@lezer/common@1.5.1": + resolution: + { + integrity: sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==, + } + + "@lezer/css@1.3.1": + resolution: + { + integrity: sha512-PYAKeUVBo3HFThruRyp/iK91SwiZJnzXh8QzkQlwijB5y+N5iB28+iLk78o2zmKqqV0uolNhCwFqB8LA7b0Svg==, + } + + "@lezer/highlight@1.2.3": + resolution: + { + integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==, + } + + "@lezer/html@1.3.13": + resolution: + { + integrity: sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==, + } + + "@lezer/javascript@1.5.4": + resolution: + { + integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==, + } + + "@lezer/lr@1.4.8": + resolution: + { + integrity: sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==, + } + + "@lezer/xml@1.0.6": + resolution: + { + integrity: sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==, + } + + "@lit-labs/ssr-dom-shim@1.5.1": + resolution: + { + integrity: sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==, + } + + "@lit/context@1.1.6": + resolution: + { + integrity: sha512-M26qDE6UkQbZA2mQ3RjJ3Gzd8TxP+/0obMgE5HfkfLhEEyYE3Bui4A5XHiGPjy0MUGAyxB3QgVuw2ciS0kHn6A==, + } + + "@lit/reactive-element@2.1.2": + resolution: + { + integrity: sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==, + } + + "@marijn/find-cluster-break@1.0.2": + resolution: + { + integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==, + } + + "@nodelib/fs.scandir@2.1.5": + resolution: + { + integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, + } + engines: { node: ">= 8" } + + "@nodelib/fs.stat@2.0.5": + resolution: + { + integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, + } + engines: { node: ">= 8" } + + "@nodelib/fs.walk@1.2.8": + resolution: + { + integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, + } + engines: { node: ">= 8" } + + "@octokit/auth-token@6.0.0": + resolution: + { + integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==, + } + engines: { node: ">= 20" } + + "@octokit/core@7.0.6": + resolution: + { + integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==, + } + engines: { node: ">= 20" } + + "@octokit/endpoint@11.0.3": + resolution: + { + integrity: sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==, + } + engines: { node: ">= 20" } + + "@octokit/graphql@9.0.3": + resolution: + { + integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==, + } + engines: { node: ">= 20" } + + "@octokit/openapi-types@27.0.0": + resolution: + { + integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==, + } + + "@octokit/plugin-paginate-rest@14.0.0": + resolution: + { + integrity: sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==, + } + engines: { node: ">= 20" } + peerDependencies: + "@octokit/core": ">=6" + + "@octokit/plugin-retry@8.1.0": + resolution: + { + integrity: sha512-O1FZgXeiGb2sowEr/hYTr6YunGdSAFWnr2fyW39Ah85H8O33ELASQxcvOFF5LE6Tjekcyu2ms4qAzJVhSaJxTw==, + } + engines: { node: ">= 20" } + peerDependencies: + "@octokit/core": ">=7" + + "@octokit/plugin-throttling@11.0.3": + resolution: + { + integrity: sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==, + } + engines: { node: ">= 20" } + peerDependencies: + "@octokit/core": ^7.0.0 + + "@octokit/request-error@7.1.0": + resolution: + { + integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==, + } + engines: { node: ">= 20" } + + "@octokit/request@10.0.7": + resolution: + { + integrity: sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==, + } + engines: { node: ">= 20" } + + "@octokit/types@16.0.0": + resolution: + { + integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==, + } + + "@patternfly/elements@4.3.1": + resolution: + { + integrity: sha512-MRVwxcam+ACyy+0Xy5igPr+LcSVRbX422NGPE4I7WRuwAEhRBA3BayyLi8mNVKXpLLZbk8EtJ17kM30PcMziMw==, + } + + "@patternfly/icons@1.0.3": + resolution: + { + integrity: sha512-8BARaCFBUZU2/TxuOQb8R2/VIpxGMnFwdw5ddT1AMnR2KSifdo+d05SgZtVmFkOIAOA0oCo/YKRgSORDA47wig==, + } + + "@patternfly/pfe-core@5.0.6": + resolution: + { + integrity: sha512-95j0BDltTTVQzOSIqpiZXQYzm1kuwqpHeB/7/QOjmZP3wtvYcaccnE/2dldKF68rn0Rwgk9xbgz7MR8/CnKFgQ==, + } + + "@pkgr/core@0.2.9": + resolution: + { + integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==, + } + engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + + "@pnpm/config.env-replace@1.1.0": + resolution: + { + integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==, + } + engines: { node: ">=12.22.0" } + + "@pnpm/network.ca-file@1.0.2": + resolution: + { + integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==, + } + engines: { node: ">=12.22.0" } + + "@pnpm/npm-conf@3.0.2": + resolution: + { + integrity: sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==, + } + engines: { node: ">=12" } + + "@polka/url@1.0.0-next.29": + resolution: + { + integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==, + } + + "@rollup/plugin-babel@5.3.1": + resolution: + { + integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==, + } + engines: { node: ">= 10.0.0" } + peerDependencies: + "@babel/core": ^7.0.0 + "@types/babel__core": ^7.1.9 + rollup: ^1.20.0||^2.0.0 + peerDependenciesMeta: + "@types/babel__core": + optional: true + + "@rollup/plugin-node-resolve@15.3.1": + resolution: + { + integrity: sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==, + } + engines: { node: ">=14.0.0" } + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + "@rollup/plugin-replace@2.4.2": + resolution: + { + integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==, + } + peerDependencies: + rollup: ^1.20.0 || ^2.0.0 + + "@rollup/plugin-terser@0.4.4": + resolution: + { + integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==, + } + engines: { node: ">=14.0.0" } + peerDependencies: + rollup: ^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + "@rollup/pluginutils@3.1.0": + resolution: + { + integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==, + } + engines: { node: ">= 8.0.0" } + peerDependencies: + rollup: ^1.20.0||^2.0.0 + + "@rollup/pluginutils@5.3.0": + resolution: + { + integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==, + } + engines: { node: ">=14.0.0" } + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + "@rollup/rollup-android-arm-eabi@4.57.1": + resolution: + { + integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==, + } + cpu: [arm] + os: [android] + + "@rollup/rollup-android-arm64@4.57.1": + resolution: + { + integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==, + } + cpu: [arm64] + os: [android] + + "@rollup/rollup-darwin-arm64@4.57.1": + resolution: + { + integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==, + } + cpu: [arm64] + os: [darwin] + + "@rollup/rollup-darwin-x64@4.57.1": + resolution: + { + integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==, + } + cpu: [x64] + os: [darwin] + + "@rollup/rollup-freebsd-arm64@4.57.1": + resolution: + { + integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==, + } + cpu: [arm64] + os: [freebsd] + + "@rollup/rollup-freebsd-x64@4.57.1": + resolution: + { + integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==, + } + cpu: [x64] + os: [freebsd] + + "@rollup/rollup-linux-arm-gnueabihf@4.57.1": + resolution: + { + integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==, + } + cpu: [arm] + os: [linux] + libc: [glibc] + + "@rollup/rollup-linux-arm-musleabihf@4.57.1": + resolution: + { + integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==, + } + cpu: [arm] + os: [linux] + libc: [musl] + + "@rollup/rollup-linux-arm64-gnu@4.57.1": + resolution: + { + integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==, + } + cpu: [arm64] + os: [linux] + libc: [glibc] + + "@rollup/rollup-linux-arm64-musl@4.57.1": + resolution: + { + integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==, + } + cpu: [arm64] + os: [linux] + libc: [musl] + + "@rollup/rollup-linux-loong64-gnu@4.57.1": + resolution: + { + integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==, + } + cpu: [loong64] + os: [linux] + libc: [glibc] + + "@rollup/rollup-linux-loong64-musl@4.57.1": + resolution: + { + integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==, + } + cpu: [loong64] + os: [linux] + libc: [musl] + + "@rollup/rollup-linux-ppc64-gnu@4.57.1": + resolution: + { + integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==, + } + cpu: [ppc64] + os: [linux] + libc: [glibc] + + "@rollup/rollup-linux-ppc64-musl@4.57.1": + resolution: + { + integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==, + } + cpu: [ppc64] + os: [linux] + libc: [musl] + + "@rollup/rollup-linux-riscv64-gnu@4.57.1": + resolution: + { + integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==, + } + cpu: [riscv64] + os: [linux] + libc: [glibc] + + "@rollup/rollup-linux-riscv64-musl@4.57.1": + resolution: + { + integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==, + } + cpu: [riscv64] + os: [linux] + libc: [musl] + + "@rollup/rollup-linux-s390x-gnu@4.57.1": + resolution: + { + integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==, + } + cpu: [s390x] + os: [linux] + libc: [glibc] + + "@rollup/rollup-linux-x64-gnu@4.57.1": + resolution: + { + integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==, + } + cpu: [x64] + os: [linux] + libc: [glibc] + + "@rollup/rollup-linux-x64-musl@4.57.1": + resolution: + { + integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==, + } + cpu: [x64] + os: [linux] + libc: [musl] + + "@rollup/rollup-openbsd-x64@4.57.1": + resolution: + { + integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==, + } + cpu: [x64] + os: [openbsd] + + "@rollup/rollup-openharmony-arm64@4.57.1": + resolution: + { + integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==, + } + cpu: [arm64] + os: [openharmony] + + "@rollup/rollup-win32-arm64-msvc@4.57.1": + resolution: + { + integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==, + } + cpu: [arm64] + os: [win32] + + "@rollup/rollup-win32-ia32-msvc@4.57.1": + resolution: + { + integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==, + } + cpu: [ia32] + os: [win32] + + "@rollup/rollup-win32-x64-gnu@4.57.1": + resolution: + { + integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==, + } + cpu: [x64] + os: [win32] + + "@rollup/rollup-win32-x64-msvc@4.57.1": + resolution: + { + integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==, + } + cpu: [x64] + os: [win32] + + "@sec-ant/readable-stream@0.4.1": + resolution: + { + integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==, + } + + "@semantic-release/changelog@6.0.3": + resolution: + { + integrity: sha512-dZuR5qByyfe3Y03TpmCvAxCyTnp7r5XwtHRf/8vD9EAn4ZWbavUX8adMtXYzE86EVh0gyLA7lm5yW4IV30XUag==, + } + engines: { node: ">=14.17" } + peerDependencies: + semantic-release: ">=18.0.0" + + "@semantic-release/commit-analyzer@13.0.1": + resolution: + { + integrity: sha512-wdnBPHKkr9HhNhXOhZD5a2LNl91+hs8CC2vsAVYxtZH3y0dV3wKn+uZSN61rdJQZ8EGxzWB3inWocBHV9+u/CQ==, + } + engines: { node: ">=20.8.1" } + peerDependencies: + semantic-release: ">=20.1.0" + + "@semantic-release/error@3.0.0": + resolution: + { + integrity: sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==, + } + engines: { node: ">=14.17" } + + "@semantic-release/error@4.0.0": + resolution: + { + integrity: sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==, + } + engines: { node: ">=18" } + + "@semantic-release/exec@7.1.0": + resolution: + { + integrity: sha512-4ycZ2atgEUutspPZ2hxO6z8JoQt4+y/kkHvfZ1cZxgl9WKJId1xPj+UadwInj+gMn2Gsv+fLnbrZ4s+6tK2TFQ==, + } + engines: { node: ">=20.8.1" } + peerDependencies: + semantic-release: ">=24.1.0" + + "@semantic-release/git@10.0.1": + resolution: + { + integrity: sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==, + } + engines: { node: ">=14.17" } + peerDependencies: + semantic-release: ">=18.0.0" + + "@semantic-release/github@12.0.6": + resolution: + { + integrity: sha512-aYYFkwHW3c6YtHwQF0t0+lAjlU+87NFOZuH2CvWFD0Ylivc7MwhZMiHOJ0FMpIgPpCVib/VUAcOwvrW0KnxQtA==, + } + engines: { node: ^22.14.0 || >= 24.10.0 } + peerDependencies: + semantic-release: ">=24.1.0" + + "@semantic-release/gitlab@13.3.0": + resolution: + { + integrity: sha512-E0q4qbTdVQZW8Siqz1YQjfSSXE2U/Z7lJasUlpHGRu+OYXsLCIU6o18scquofyP7CBxyKLIx7TDiLVRko1ekGw==, + } + engines: { node: ">=20.8.1" } + peerDependencies: + semantic-release: ">=20.1.0" + + "@semantic-release/npm@13.1.4": + resolution: + { + integrity: sha512-z5Fn9ftK1QQgFxMSuOd3DtYbTl4hWI2trCEvZcEJMQJy1/OBR0WHcxqzfVun455FSkHML8KgvPxJEa9MtZIBsg==, + } + engines: { node: ^22.14.0 || >= 24.10.0 } + peerDependencies: + semantic-release: ">=20.1.0" + + "@semantic-release/release-notes-generator@14.1.0": + resolution: + { + integrity: sha512-CcyDRk7xq+ON/20YNR+1I/jP7BYKICr1uKd1HHpROSnnTdGqOTburi4jcRiTYz0cpfhxSloQO3cGhnoot7IEkA==, + } + engines: { node: ">=20.8.1" } + peerDependencies: + semantic-release: ">=20.1.0" + + "@sindresorhus/is@4.6.0": + resolution: + { + integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==, + } + engines: { node: ">=10" } + + "@sindresorhus/is@7.2.0": + resolution: + { + integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==, + } + engines: { node: ">=18" } + + "@sindresorhus/merge-streams@2.3.0": + resolution: + { + integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==, + } + engines: { node: ">=18" } + + "@sindresorhus/merge-streams@4.0.0": + resolution: + { + integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==, + } + engines: { node: ">=18" } + + "@stencil/core@2.5.2": + resolution: + { + integrity: sha512-bgjPXkSzzg1WnTgVUm6m5ZzpKt602WmA/QljODAW1xVN40OHJdbGblzF/F6MFzqv2c5Cy30CB41arc8qADIdcQ==, + } + engines: { node: ">=12.10.0", npm: ">=6.0.0" } + hasBin: true + + "@surma/rollup-plugin-off-main-thread@2.2.3": + resolution: + { + integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==, + } + + "@tailwindcss/forms@0.5.11": + resolution: + { + integrity: sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA==, + } + peerDependencies: + tailwindcss: ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" + + "@tailwindcss/typography@0.5.19": + resolution: + { + integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==, + } + peerDependencies: + tailwindcss: ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + + "@types/eslint@9.6.1": + resolution: + { + integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==, + } + + "@types/esrecurse@4.3.1": + resolution: + { + integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==, + } + + "@types/estree@0.0.39": + resolution: + { + integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==, + } + + "@types/estree@1.0.8": + resolution: + { + integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, + } + + "@types/fscreen@1.0.4": + resolution: + { + integrity: sha512-TsjxyAUvlvuQyao9vNk0yES4nY07K9xoAbkhgXU948JG39EqlLxniWuW9OiZde9Q8ACSpu3fmbXXRAfb/l/HqQ==, + } + + "@types/geojson@7946.0.16": + resolution: + { + integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==, + } + + "@types/http-cache-semantics@4.2.0": + resolution: + { + integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==, + } + + "@types/json-schema@7.0.15": + resolution: + { + integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, + } + + "@types/leaflet@1.9.21": + resolution: + { + integrity: sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==, + } + + "@types/node@24.7.0": + resolution: + { + integrity: sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==, + } + + "@types/normalize-package-data@2.4.4": + resolution: + { + integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==, + } + + "@types/resolve@1.20.2": + resolution: + { + integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==, + } + + "@types/trusted-types@2.0.7": + resolution: + { + integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==, + } + + "@typescript-eslint/eslint-plugin@8.56.0": + resolution: + { + integrity: sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + "@typescript-eslint/parser": ^8.56.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.0.0" + + "@typescript-eslint/parser@8.56.0": + resolution: + { + integrity: sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.0.0" + + "@typescript-eslint/project-service@8.56.0": + resolution: + { + integrity: sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + + "@typescript-eslint/scope-manager@8.56.0": + resolution: + { + integrity: sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@typescript-eslint/tsconfig-utils@8.56.0": + resolution: + { + integrity: sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + + "@typescript-eslint/type-utils@8.56.0": + resolution: + { + integrity: sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.0.0" + + "@typescript-eslint/types@8.56.0": + resolution: + { + integrity: sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@typescript-eslint/typescript-estree@8.56.0": + resolution: + { + integrity: sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + + "@typescript-eslint/utils@8.56.0": + resolution: + { + integrity: sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.0.0" + + "@typescript-eslint/visitor-keys@8.56.0": + resolution: + { + integrity: sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@vime/core@5.4.1": + resolution: + { + integrity: sha512-ZFpV3xqZJ5tvh5rZOYKRh8zFzNIKr2ZcK6L75nJjFjbWt/ZmFF2nMBxtD9/hC4Xjk9v7hp1+P9cmctL674VFgA==, + } + + acorn-jsx@5.3.2: + resolution: + { + integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, + } + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: + { + integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==, + } + engines: { node: ">=0.4.0" } + hasBin: true + + agent-base@7.1.4: + resolution: + { + integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==, + } + engines: { node: ">= 14" } + + aggregate-error@3.1.0: + resolution: + { + integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==, + } + engines: { node: ">=8" } + + aggregate-error@5.0.0: + resolution: + { + integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==, + } + engines: { node: ">=18" } + + ajv@6.12.6: + resolution: + { + integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, + } + + ajv@8.18.0: + resolution: + { + integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==, + } + + all-contributors-cli@6.26.1: + resolution: + { + integrity: sha512-Ymgo3FJACRBEd1eE653FD1J/+uD0kqpUNYfr9zNC1Qby0LgbhDBzB3EF6uvkAbYpycStkk41J+0oo37Lc02yEw==, + } + engines: { node: ">=4" } + hasBin: true + + ansi-escapes@4.3.2: + resolution: + { + integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==, + } + engines: { node: ">=8" } + + ansi-escapes@7.3.0: + resolution: + { + integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==, + } + engines: { node: ">=18" } + + ansi-regex@5.0.1: + resolution: + { + integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, + } + engines: { node: ">=8" } + + ansi-regex@6.2.2: + resolution: + { + integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==, + } + engines: { node: ">=12" } + + ansi-styles@3.2.1: + resolution: + { + integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==, + } + engines: { node: ">=4" } + + ansi-styles@4.3.0: + resolution: + { + integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, + } + engines: { node: ">=8" } + + ansi-styles@6.2.3: + resolution: + { + integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==, + } + engines: { node: ">=12" } + + ansis@4.2.0: + resolution: + { + integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==, + } + engines: { node: ">=14" } + + any-promise@1.3.0: + resolution: + { + integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==, + } + + anymatch@3.1.3: + resolution: + { + integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==, + } + engines: { node: ">= 8" } + + arg@5.0.2: + resolution: + { + integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==, + } + + argparse@2.0.1: + resolution: + { + integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, + } + + argv-formatter@1.0.0: + resolution: + { + integrity: sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==, + } + + array-buffer-byte-length@1.0.2: + resolution: + { + integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==, + } + engines: { node: ">= 0.4" } + + array-ify@1.0.0: + resolution: + { + integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==, + } + + arraybuffer.prototype.slice@1.0.4: + resolution: + { + integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==, + } + engines: { node: ">= 0.4" } + + astral-regex@2.0.0: + resolution: + { + integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==, + } + engines: { node: ">=8" } + + async-function@1.0.0: + resolution: + { + integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==, + } + engines: { node: ">= 0.4" } + + async@3.2.6: + resolution: + { + integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==, + } + + at-least-node@1.0.0: + resolution: + { + integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==, + } + engines: { node: ">= 4.0.0" } + + autoprefixer@10.4.24: + resolution: + { + integrity: sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==, + } + engines: { node: ^10 || ^12 || >=14 } + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: + { + integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==, + } + engines: { node: ">= 0.4" } + + babel-plugin-polyfill-corejs2@0.4.15: + resolution: + { + integrity: sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==, + } + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.14.0: + resolution: + { + integrity: sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==, + } + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.6: + resolution: + { + integrity: sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==, + } + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + + balanced-match@1.0.2: + resolution: + { + integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, + } + + balanced-match@3.0.1: + resolution: + { + integrity: sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==, + } + engines: { node: ">= 16" } + + balanced-match@4.0.3: + resolution: + { + integrity: sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==, + } + engines: { node: 20 || >=22 } + + base64-js@1.3.1: + resolution: + { + integrity: sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==, + } + + base64-js@1.5.1: + resolution: + { + integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, + } + + baseline-browser-mapping@2.10.0: + resolution: + { + integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==, + } + engines: { node: ">=6.0.0" } + hasBin: true + + before-after-hook@4.0.0: + resolution: + { + integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==, + } + + binary-extensions@2.3.0: + resolution: + { + integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==, + } + engines: { node: ">=8" } + + birpc@2.9.0: + resolution: + { + integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==, + } + + bl@4.1.0: + resolution: + { + integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==, + } + + boolbase@1.0.0: + resolution: + { + integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==, + } + + bottleneck@2.19.5: + resolution: + { + integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==, + } + + brace-expansion@1.1.12: + resolution: + { + integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==, + } + + brace-expansion@2.0.2: + resolution: + { + integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==, + } + + brace-expansion@5.0.2: + resolution: + { + integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==, + } + engines: { node: 20 || >=22 } + + braces@3.0.3: + resolution: + { + integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==, + } + engines: { node: ">=8" } + + brotli@1.3.3: + resolution: + { + integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==, + } + + browserslist@4.28.1: + resolution: + { + integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==, + } + engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + hasBin: true + + buffer-from@1.1.2: + resolution: + { + integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==, + } + + buffer@5.7.1: + resolution: + { + integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==, + } + + bundle-name@4.1.0: + resolution: + { + integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==, + } + engines: { node: ">=18" } + + byte-counter@0.1.0: + resolution: + { + integrity: sha512-jheRLVMeUKrDBjVw2O5+k4EvR4t9wtxHL+bo/LxfkxsVeuGMy3a5SEGgXdAFA4FSzTrU8rQXQIrsZ3oBq5a0pQ==, + } + engines: { node: ">=20" } + + cacheable-lookup@7.0.0: + resolution: + { + integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==, + } + engines: { node: ">=14.16" } + + cacheable-request@13.0.18: + resolution: + { + integrity: sha512-rFWadDRKJs3s2eYdXlGggnBZKG7MTblkFBB0YllFds+UYnfogDp2wcR6JN97FhRkHTvq59n2vhNoHNZn29dh/Q==, + } + engines: { node: ">=18" } + + cacheable@2.3.2: + resolution: + { + integrity: sha512-w+ZuRNmex9c1TR9RcsxbfTKCjSL0rh1WA5SABbrWprIHeNBdmyQLSYonlDy9gpD+63XT8DgZ/wNh1Smvc9WnJA==, + } + + cachedir@2.3.0: + resolution: + { + integrity: sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==, + } + engines: { node: ">=6" } + + call-bind-apply-helpers@1.0.2: + resolution: + { + integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==, + } + engines: { node: ">= 0.4" } + + call-bind@1.0.8: + resolution: + { + integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==, + } + engines: { node: ">= 0.4" } + + call-bound@1.0.4: + resolution: + { + integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==, + } + engines: { node: ">= 0.4" } + + callsites@3.1.0: + resolution: + { + integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, + } + engines: { node: ">=6" } + + camelcase-css@2.0.1: + resolution: + { + integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==, + } + engines: { node: ">= 6" } + + camelcase@5.3.1: + resolution: + { + integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==, + } + engines: { node: ">=6" } + + caniuse-api@3.0.0: + resolution: + { + integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==, + } + + caniuse-lite@1.0.30001770: + resolution: + { + integrity: sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==, + } + + chalk@2.4.2: + resolution: + { + integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==, + } + engines: { node: ">=4" } + + chalk@4.1.2: + resolution: + { + integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, + } + engines: { node: ">=10" } + + chalk@5.6.2: + resolution: + { + integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==, + } + engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } + + char-regex@1.0.2: + resolution: + { + integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==, + } + engines: { node: ">=10" } + + chardet@0.7.0: + resolution: + { + integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==, + } + + choices.js@11.1.0: + resolution: + { + integrity: sha512-mIt0uLhedHg2ea/K2PACrVpt391vRGHuOoctPAiHcyemezwzNMxj7jOzNEk8e7EbjLh0S0sspDkSCADOKz9kcw==, + } + + chokidar@3.6.0: + resolution: + { + integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==, + } + engines: { node: ">= 8.10.0" } + + ci-info@4.4.0: + resolution: + { + integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==, + } + engines: { node: ">=8" } + + clean-stack@2.2.0: + resolution: + { + integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==, + } + engines: { node: ">=6" } + + clean-stack@5.3.0: + resolution: + { + integrity: sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==, + } + engines: { node: ">=14.16" } + + cli-cursor@3.1.0: + resolution: + { + integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==, + } + engines: { node: ">=8" } + + cli-cursor@5.0.0: + resolution: + { + integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==, + } + engines: { node: ">=18" } + + cli-highlight@2.1.11: + resolution: + { + integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==, + } + engines: { node: ">=8.0.0", npm: ">=5.0.0" } + hasBin: true + + cli-spinners@2.9.2: + resolution: + { + integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==, + } + engines: { node: ">=6" } + + cli-table3@0.6.5: + resolution: + { + integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==, + } + engines: { node: 10.* || >= 12.* } + + cli-truncate@5.1.1: + resolution: + { + integrity: sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==, + } + engines: { node: ">=20" } + + cli-width@3.0.0: + resolution: + { + integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==, + } + engines: { node: ">= 10" } + + cliui@6.0.0: + resolution: + { + integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==, + } + + cliui@7.0.4: + resolution: + { + integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==, + } + + cliui@8.0.1: + resolution: + { + integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==, + } + engines: { node: ">=12" } + + cliui@9.0.1: + resolution: + { + integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==, + } + engines: { node: ">=20" } + + clone@1.0.4: + resolution: + { + integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==, + } + engines: { node: ">=0.8" } + + codemirror@6.0.2: + resolution: + { + integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==, + } + + color-convert@1.9.3: + resolution: + { + integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==, + } + + color-convert@2.0.1: + resolution: + { + integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, + } + engines: { node: ">=7.0.0" } + + color-name@1.1.3: + resolution: + { + integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==, + } + + color-name@1.1.4: + resolution: + { + integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, + } + + colord@2.9.3: + resolution: + { + integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==, + } + + colorette@2.0.20: + resolution: + { + integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==, + } + + commander@11.1.0: + resolution: + { + integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==, + } + engines: { node: ">=16" } + + commander@14.0.3: + resolution: + { + integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==, + } + engines: { node: ">=20" } + + commander@2.20.3: + resolution: + { + integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==, + } + + commander@4.1.1: + resolution: + { + integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==, + } + engines: { node: ">= 6" } + + commander@7.2.0: + resolution: + { + integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==, + } + engines: { node: ">= 10" } + + commitizen@4.3.1: + resolution: + { + integrity: sha512-gwAPAVTy/j5YcOOebcCRIijn+mSjWJC+IYKivTu6aG8Ei/scoXgfsMRnuAk6b0GRste2J4NGxVdMN3ZpfNaVaw==, + } + engines: { node: ">= 12" } + hasBin: true + + common-tags@1.8.2: + resolution: + { + integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==, + } + engines: { node: ">=4.0.0" } + + compare-func@2.0.0: + resolution: + { + integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==, + } + + concat-map@0.0.1: + resolution: + { + integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, + } + + config-chain@1.1.13: + resolution: + { + integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==, + } + + conventional-changelog-angular@8.1.0: + resolution: + { + integrity: sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==, + } + engines: { node: ">=18" } + + conventional-changelog-conventionalcommits@9.1.0: + resolution: + { + integrity: sha512-MnbEysR8wWa8dAEvbj5xcBgJKQlX/m0lhS8DsyAAWDHdfs2faDJxTgzRYlRYpXSe7UiKrIIlB4TrBKU9q9DgkA==, + } + engines: { node: ">=18" } + + conventional-changelog-writer@8.2.0: + resolution: + { + integrity: sha512-Y2aW4596l9AEvFJRwFGJGiQjt2sBYTjPD18DdvxX9Vpz0Z7HQ+g1Z+6iYDAm1vR3QOJrDBkRHixHK/+FhkR6Pw==, + } + engines: { node: ">=18" } + hasBin: true + + conventional-commit-types@3.0.0: + resolution: + { + integrity: sha512-SmmCYnOniSsAa9GqWOeLqc179lfr5TRu5b4QFDkbsrJ5TZjPJx85wtOr3zn+1dbeNiXDKGPbZ72IKbPhLXh/Lg==, + } + + conventional-commits-filter@5.0.0: + resolution: + { + integrity: sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==, + } + engines: { node: ">=18" } + + conventional-commits-parser@6.2.1: + resolution: + { + integrity: sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==, + } + engines: { node: ">=18" } + hasBin: true + + convert-hrtime@5.0.0: + resolution: + { + integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==, + } + engines: { node: ">=12" } + + convert-source-map@2.0.0: + resolution: + { + integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, + } + + core-js-compat@3.48.0: + resolution: + { + integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==, + } + + core-js@3.48.0: + resolution: + { + integrity: sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==, + } + + core-util-is@1.0.3: + resolution: + { + integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==, + } + + cosmiconfig-typescript-loader@6.2.0: + resolution: + { + integrity: sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ==, + } + engines: { node: ">=v18" } + peerDependencies: + "@types/node": "*" + cosmiconfig: ">=9" + typescript: ">=5" + + cosmiconfig@9.0.0: + resolution: + { + integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==, + } + engines: { node: ">=14" } + peerDependencies: + typescript: ">=4.9.5" + peerDependenciesMeta: + typescript: + optional: true + + crelt@1.0.6: + resolution: + { + integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==, + } + + cross-env@10.1.0: + resolution: + { + integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==, + } + engines: { node: ">=20" } + hasBin: true + + cross-spawn@7.0.6: + resolution: + { + integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, + } + engines: { node: ">= 8" } + + crypto-js@4.2.0: + resolution: + { + integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==, + } + + crypto-random-string@2.0.0: + resolution: + { + integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==, + } + engines: { node: ">=8" } + + crypto-random-string@4.0.0: + resolution: + { + integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==, + } + engines: { node: ">=12" } + + css-blank-pseudo@8.0.1: + resolution: + { + integrity: sha512-C5B2e5hCM4llrQkUms+KnWEMVW8K1n2XvX9G7ppfMZJQ7KAS/4rNnkP1Cs+HhWriOz1mWWTMFD4j1J7s31Dgug==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + css-declaration-sorter@7.3.1: + resolution: + { + integrity: sha512-gz6x+KkgNCjxq3Var03pRYLhyNfwhkKF1g/yoLgDNtFvVu0/fOLV9C8fFEZRjACp/XQLumjAYo7JVjzH3wLbxA==, + } + engines: { node: ^14 || ^16 || >=18 } + peerDependencies: + postcss: ^8.0.9 + + css-functions-list@3.3.3: + resolution: + { + integrity: sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==, + } + engines: { node: ">=12" } + + css-has-pseudo@8.0.0: + resolution: + { + integrity: sha512-Uz/bsHRbOeir/5Oeuz85tq/yLJLxX+3dpoRdjNTshs6jjqwUg8XaEZGDd0ci3fw7l53Srw0EkJ8mYan0eW5uGQ==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + css-prefers-color-scheme@11.0.0: + resolution: + { + integrity: sha512-fv0mgtwUhh2m9iio3Kxc2CkrogjIaRdMFaaqyzSFdii17JF4cfPyMNX72B15ZW2Nrr/NZUpxI4dec1VMHYJvdw==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + css-select@5.2.2: + resolution: + { + integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==, + } + + css-tree@2.2.1: + resolution: + { + integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==, + } + engines: { node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: ">=7.0.0" } + + css-tree@3.1.0: + resolution: + { + integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==, + } + engines: { node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0 } + + css-what@6.2.2: + resolution: + { + integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==, + } + engines: { node: ">= 6" } + + cssdb@8.7.1: + resolution: + { + integrity: sha512-+F6LKx48RrdGOtE4DT5jz7Uo+VeyKXpK797FAevIkzjV8bMHz6xTO5F7gNDcRCHmPgD5jj2g6QCsY9zmVrh38A==, + } + + cssesc@3.0.0: + resolution: + { + integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==, + } + engines: { node: ">=4" } + hasBin: true + + cssnano-preset-default@7.0.10: + resolution: + { + integrity: sha512-6ZBjW0Lf1K1Z+0OKUAUpEN62tSXmYChXWi2NAA0afxEVsj9a+MbcB1l5qel6BHJHmULai2fCGRthCeKSFbScpA==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + cssnano-utils@5.0.1: + resolution: + { + integrity: sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + cssnano@7.1.2: + resolution: + { + integrity: sha512-HYOPBsNvoiFeR1eghKD5C3ASm64v9YVyJB4Ivnl2gqKoQYvjjN/G0rztvKQq8OxocUtC6sjqY8jwYngIB4AByA==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + csso@5.0.5: + resolution: + { + integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==, + } + engines: { node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: ">=7.0.0" } + + cz-conventional-changelog@3.3.0: + resolution: + { + integrity: sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw==, + } + engines: { node: ">= 10" } + + d3-array@3.2.4: + resolution: + { + integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==, + } + engines: { node: ">=12" } + + d3-color@3.1.0: + resolution: + { + integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==, + } + engines: { node: ">=12" } + + d3-dispatch@3.0.1: + resolution: + { + integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==, + } + engines: { node: ">=12" } + + d3-ease@3.0.1: + resolution: + { + integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==, + } + engines: { node: ">=12" } + + d3-force@3.0.0: + resolution: + { + integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==, + } + engines: { node: ">=12" } + + d3-geo-projection@4.0.0: + resolution: + { + integrity: sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==, + } + engines: { node: ">=12" } + hasBin: true + + d3-geo@3.1.1: + resolution: + { + integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==, + } + engines: { node: ">=12" } + + d3-interpolate@3.0.1: + resolution: + { + integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==, + } + engines: { node: ">=12" } + + d3-quadtree@3.0.1: + resolution: + { + integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==, + } + engines: { node: ">=12" } + + d3-selection@3.0.0: + resolution: + { + integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==, + } + engines: { node: ">=12" } + + d3-timer@3.0.1: + resolution: + { + integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==, + } + engines: { node: ">=12" } + + d3-transition@3.0.1: + resolution: + { + integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==, + } + engines: { node: ">=12" } + peerDependencies: + d3-selection: 2 - 3 + + dargs@8.1.0: + resolution: + { + integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==, + } + engines: { node: ">=12" } + + data-view-buffer@1.0.2: + resolution: + { + integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==, + } + engines: { node: ">= 0.4" } + + data-view-byte-length@1.0.2: + resolution: + { + integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==, + } + engines: { node: ">= 0.4" } + + data-view-byte-offset@1.0.1: + resolution: + { + integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==, + } + engines: { node: ">= 0.4" } + + debug@4.4.3: + resolution: + { + integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==, + } + engines: { node: ">=6.0" } + peerDependencies: + supports-color: "*" + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@1.2.0: + resolution: + { + integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==, + } + engines: { node: ">=0.10.0" } + + decompress-response@10.0.0: + resolution: + { + integrity: sha512-oj7KWToJuuxlPr7VV0vabvxEIiqNMo+q0NueIiL3XhtwC6FVOX7Hr1c0C4eD0bmf7Zr+S/dSf2xvkH3Ad6sU3Q==, + } + engines: { node: ">=20" } + + dedent@0.7.0: + resolution: + { + integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==, + } + + deep-equal@1.1.2: + resolution: + { + integrity: sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==, + } + engines: { node: ">= 0.4" } + + deep-extend@0.6.0: + resolution: + { + integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==, + } + engines: { node: ">=4.0.0" } + + deep-is@0.1.4: + resolution: + { + integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, + } + + deepmerge@4.3.1: + resolution: + { + integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==, + } + engines: { node: ">=0.10.0" } + + default-browser-id@5.0.1: + resolution: + { + integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==, + } + engines: { node: ">=18" } + + default-browser@5.5.0: + resolution: + { + integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==, + } + engines: { node: ">=18" } + + defaults@1.0.4: + resolution: + { + integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==, + } + + define-data-property@1.1.4: + resolution: + { + integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==, + } + engines: { node: ">= 0.4" } + + define-lazy-prop@3.0.0: + resolution: + { + integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==, + } + engines: { node: ">=12" } + + define-properties@1.2.1: + resolution: + { + integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==, + } + engines: { node: ">= 0.4" } + + detect-file@1.0.0: + resolution: + { + integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==, + } + engines: { node: ">=0.10.0" } + + detect-indent@6.1.0: + resolution: + { + integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==, + } + engines: { node: ">=8" } + + detect-libc@2.1.2: + resolution: + { + integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==, + } + engines: { node: ">=8" } + + dfa@1.2.0: + resolution: + { + integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==, + } + + didyoumean@1.2.2: + resolution: + { + integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==, + } + + dir-glob@3.0.1: + resolution: + { + integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==, + } + engines: { node: ">=8" } + + dlv@1.1.3: + resolution: + { + integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==, + } + + dom-serializer@2.0.0: + resolution: + { + integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==, + } + + domelementtype@2.3.0: + resolution: + { + integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==, + } + + domhandler@5.0.3: + resolution: + { + integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==, + } + engines: { node: ">= 4" } + + domutils@3.2.2: + resolution: + { + integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==, + } + + dot-prop@5.3.0: + resolution: + { + integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==, + } + engines: { node: ">=8" } + + dunder-proto@1.0.1: + resolution: + { + integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==, + } + engines: { node: ">= 0.4" } + + duplexer2@0.1.4: + resolution: + { + integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==, + } + + ejs@3.1.10: + resolution: + { + integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==, + } + engines: { node: ">=0.10.0" } + hasBin: true + + electron-to-chromium@1.5.286: + resolution: + { + integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==, + } + + emoji-regex@10.6.0: + resolution: + { + integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==, + } + + emoji-regex@8.0.0: + resolution: + { + integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, + } + + emojilib@2.4.0: + resolution: + { + integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==, + } + + entities@4.5.0: + resolution: + { + integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==, + } + engines: { node: ">=0.12" } + + env-ci@11.2.0: + resolution: + { + integrity: sha512-D5kWfzkmaOQDioPmiviWAVtKmpPT4/iJmMVQxWxMPJTFyTkdc5JQUfc5iXEeWxcOdsYTKSAiA/Age4NUOqKsRA==, + } + engines: { node: ^18.17 || >=20.6.1 } + + env-paths@2.2.1: + resolution: + { + integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==, + } + engines: { node: ">=6" } + + environment@1.1.0: + resolution: + { + integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==, + } + engines: { node: ">=18" } + + error-ex@1.3.4: + resolution: + { + integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==, + } + + error-stack-parser-es@1.0.5: + resolution: + { + integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==, + } + + es-abstract@1.24.1: + resolution: + { + integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==, + } + engines: { node: ">= 0.4" } + + es-define-property@1.0.1: + resolution: + { + integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==, + } + engines: { node: ">= 0.4" } + + es-errors@1.3.0: + resolution: + { + integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==, + } + engines: { node: ">= 0.4" } + + es-object-atoms@1.1.1: + resolution: + { + integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==, + } + engines: { node: ">= 0.4" } + + es-set-tostringtag@2.1.0: + resolution: + { + integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==, + } + engines: { node: ">= 0.4" } + + es-to-primitive@1.3.0: + resolution: + { + integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==, + } + engines: { node: ">= 0.4" } + + esbuild@0.27.3: + resolution: + { + integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==, + } + engines: { node: ">=18" } + hasBin: true + + escalade@3.2.0: + resolution: + { + integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, + } + engines: { node: ">=6" } + + escape-string-regexp@1.0.5: + resolution: + { + integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==, + } + engines: { node: ">=0.8.0" } + + escape-string-regexp@4.0.0: + resolution: + { + integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, + } + engines: { node: ">=10" } + + escape-string-regexp@5.0.0: + resolution: + { + integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==, + } + engines: { node: ">=12" } + + eslint-config-prettier@10.1.8: + resolution: + { + integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==, + } + hasBin: true + peerDependencies: + eslint: ">=7.0.0" + + eslint-plugin-prettier@5.5.5: + resolution: + { + integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==, + } + engines: { node: ^14.18.0 || >=16.0.0 } + peerDependencies: + "@types/eslint": ">=8.0.0" + eslint: ">=8.0.0" + eslint-config-prettier: ">= 7.0.0 <10.0.0 || >=10.1.0" + prettier: ">=3.0.0" + peerDependenciesMeta: + "@types/eslint": + optional: true + eslint-config-prettier: + optional: true + + eslint-scope@9.1.0: + resolution: + { + integrity: sha512-CkWE42hOJsNj9FJRaoMX9waUFYhqY4jmyLFdAdzZr6VaCg3ynLYx4WnOdkaIifGfH4gsUcBTn4OZbHXkpLD0FQ==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + eslint-visitor-keys@3.4.3: + resolution: + { + integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + + eslint-visitor-keys@4.2.1: + resolution: + { + integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + eslint-visitor-keys@5.0.0: + resolution: + { + integrity: sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + eslint@10.0.0: + resolution: + { + integrity: sha512-O0piBKY36YSJhlFSG8p9VUdPV/SxxS4FYDWVpr/9GJuMaepzwlf4J8I4ov1b+ySQfDTPhc3DtLaxcT1fN0yqCg==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + hasBin: true + peerDependencies: + jiti: "*" + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: + { + integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + espree@11.1.0: + resolution: + { + integrity: sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + esquery@1.7.0: + resolution: + { + integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==, + } + engines: { node: ">=0.10" } + + esrecurse@4.3.0: + resolution: + { + integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==, + } + engines: { node: ">=4.0" } + + estraverse@5.3.0: + resolution: + { + integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, + } + engines: { node: ">=4.0" } + + estree-walker@1.0.1: + resolution: + { + integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==, + } + + estree-walker@2.0.2: + resolution: + { + integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==, + } + + esutils@2.0.3: + resolution: + { + integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, + } + engines: { node: ">=0.10.0" } + + eventemitter3@5.0.4: + resolution: + { + integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==, + } + + execa@5.1.1: + resolution: + { + integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==, + } + engines: { node: ">=10" } + + execa@8.0.1: + resolution: + { + integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==, + } + engines: { node: ">=16.17" } + + execa@9.6.1: + resolution: + { + integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==, + } + engines: { node: ^18.19.0 || >=20.5.0 } + + expand-tilde@2.0.2: + resolution: + { + integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==, + } + engines: { node: ">=0.10.0" } + + external-editor@3.1.0: + resolution: + { + integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==, + } + engines: { node: ">=4" } + + fast-content-type-parse@3.0.0: + resolution: + { + integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==, + } + + fast-deep-equal@3.1.3: + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, + } + + fast-diff@1.3.0: + resolution: + { + integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==, + } + + fast-glob@3.3.3: + resolution: + { + integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==, + } + engines: { node: ">=8.6.0" } + + fast-json-stable-stringify@2.1.0: + resolution: + { + integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, + } + + fast-levenshtein@2.0.6: + resolution: + { + integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, + } + + fast-uri@3.1.0: + resolution: + { + integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==, + } + + fastest-levenshtein@1.0.16: + resolution: + { + integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==, + } + engines: { node: ">= 4.9.1" } + + fastq@1.20.1: + resolution: + { + integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==, + } + + fdir@6.5.0: + resolution: + { + integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==, + } + engines: { node: ">=12.0.0" } + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + figures@2.0.0: + resolution: + { + integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==, + } + engines: { node: ">=4" } + + figures@3.2.0: + resolution: + { + integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==, + } + engines: { node: ">=8" } + + figures@6.1.0: + resolution: + { + integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==, + } + engines: { node: ">=18" } + + file-entry-cache@11.1.2: + resolution: + { + integrity: sha512-N2WFfK12gmrK1c1GXOqiAJ1tc5YE+R53zvQ+t5P8S5XhnmKYVB5eZEiLNZKDSmoG8wqqbF9EXYBBW/nef19log==, + } + + file-entry-cache@8.0.0: + resolution: + { + integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==, + } + engines: { node: ">=16.0.0" } + + filelist@1.0.4: + resolution: + { + integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==, + } + + fill-range@7.1.1: + resolution: + { + integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==, + } + engines: { node: ">=8" } + + find-node-modules@2.1.3: + resolution: + { + integrity: sha512-UC2I2+nx1ZuOBclWVNdcnbDR5dlrOdVb7xNjmT/lHE+LsgztWks3dG7boJ37yTS/venXw84B/mAW9uHVoC5QRg==, + } + + find-root@1.1.0: + resolution: + { + integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==, + } + + find-up-simple@1.0.1: + resolution: + { + integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==, + } + engines: { node: ">=18" } + + find-up@2.1.0: + resolution: + { + integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==, + } + engines: { node: ">=4" } + + find-up@4.1.0: + resolution: + { + integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==, + } + engines: { node: ">=8" } + + find-up@5.0.0: + resolution: + { + integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==, + } + engines: { node: ">=10" } + + find-versions@6.0.0: + resolution: + { + integrity: sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==, + } + engines: { node: ">=18" } + + findup-sync@4.0.0: + resolution: + { + integrity: sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==, + } + engines: { node: ">= 8" } + + flat-cache@4.0.1: + resolution: + { + integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==, + } + engines: { node: ">=16" } + + flat-cache@6.1.20: + resolution: + { + integrity: sha512-AhHYqwvN62NVLp4lObVXGVluiABTHapoB57EyegZVmazN+hhGhLTn3uZbOofoTw4DSDvVCadzzyChXhOAvy8uQ==, + } + + flatpickr@4.6.13: + resolution: + { + integrity: sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==, + } + + flatted@3.3.3: + resolution: + { + integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==, + } + + for-each@0.3.5: + resolution: + { + integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==, + } + engines: { node: ">= 0.4" } + + foreground-child@3.3.1: + resolution: + { + integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==, + } + engines: { node: ">=14" } + + form-data-encoder@4.1.0: + resolution: + { + integrity: sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==, + } + engines: { node: ">= 18" } + + formdata-node@6.0.3: + resolution: + { + integrity: sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==, + } + engines: { node: ">= 18" } + + fraction.js@5.3.4: + resolution: + { + integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==, + } + + from2@2.3.0: + resolution: + { + integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==, + } + + fs-extra@11.3.3: + resolution: + { + integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==, + } + engines: { node: ">=14.14" } + + fs-extra@9.1.0: + resolution: + { + integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==, + } + engines: { node: ">=10" } + + fs.realpath@1.0.0: + resolution: + { + integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==, + } + + fscreen@1.2.0: + resolution: + { + integrity: sha512-hlq4+BU0hlPmwsFjwGGzZ+OZ9N/wq9Ljg/sq3pX+2CD7hrJsX9tJgWWK/wiNTFM212CLHWhicOoqwXyZGGetJg==, + } + + fsevents@2.3.3: + resolution: + { + integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, + } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + os: [darwin] + + function-bind@1.1.2: + resolution: + { + integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, + } + + function-timeout@1.0.2: + resolution: + { + integrity: sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==, + } + engines: { node: ">=18" } + + function.prototype.name@1.1.8: + resolution: + { + integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==, + } + engines: { node: ">= 0.4" } + + functions-have-names@1.2.3: + resolution: + { + integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==, + } + + fuse.js@7.1.0: + resolution: + { + integrity: sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==, + } + engines: { node: ">=10" } + + generator-function@2.0.1: + resolution: + { + integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==, + } + engines: { node: ">= 0.4" } + + gensync@1.0.0-beta.2: + resolution: + { + integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, + } + engines: { node: ">=6.9.0" } + + get-caller-file@2.0.5: + resolution: + { + integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==, + } + engines: { node: 6.* || 8.* || >= 10.* } + + get-east-asian-width@1.5.0: + resolution: + { + integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==, + } + engines: { node: ">=18" } + + get-intrinsic@1.3.0: + resolution: + { + integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==, + } + engines: { node: ">= 0.4" } + + get-own-enumerable-property-symbols@3.0.2: + resolution: + { + integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==, + } + + get-proto@1.0.1: + resolution: + { + integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==, + } + engines: { node: ">= 0.4" } + + get-stream@6.0.1: + resolution: + { + integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==, + } + engines: { node: ">=10" } + + get-stream@7.0.1: + resolution: + { + integrity: sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==, + } + engines: { node: ">=16" } + + get-stream@8.0.1: + resolution: + { + integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==, + } + engines: { node: ">=16" } + + get-stream@9.0.1: + resolution: + { + integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==, + } + engines: { node: ">=18" } + + get-symbol-description@1.1.0: + resolution: + { + integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==, + } + engines: { node: ">= 0.4" } + + git-log-parser@1.2.1: + resolution: + { + integrity: sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ==, + } + + git-raw-commits@4.0.0: + resolution: + { + integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==, + } + engines: { node: ">=16" } + hasBin: true + + glob-parent@5.1.2: + resolution: + { + integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, + } + engines: { node: ">= 6" } + + glob-parent@6.0.2: + resolution: + { + integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, + } + engines: { node: ">=10.13.0" } + + glob@11.1.0: + resolution: + { + integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==, + } + engines: { node: 20 || >=22 } + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + glob@13.0.5: + resolution: + { + integrity: sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw==, + } + engines: { node: 20 || >=22 } + + glob@7.2.3: + resolution: + { + integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==, + } + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + global-directory@4.0.1: + resolution: + { + integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==, + } + engines: { node: ">=18" } + + global-modules@1.0.0: + resolution: + { + integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==, + } + engines: { node: ">=0.10.0" } + + global-modules@2.0.0: + resolution: + { + integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==, + } + engines: { node: ">=6" } + + global-prefix@1.0.2: + resolution: + { + integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==, + } + engines: { node: ">=0.10.0" } + + global-prefix@3.0.0: + resolution: + { + integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==, + } + engines: { node: ">=6" } + + globals@14.0.0: + resolution: + { + integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==, + } + engines: { node: ">=18" } + + globals@17.3.0: + resolution: + { + integrity: sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==, + } + engines: { node: ">=18" } + + globalthis@1.0.4: + resolution: + { + integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==, + } + engines: { node: ">= 0.4" } + + globby@14.1.0: + resolution: + { + integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==, + } + engines: { node: ">=18" } + + globby@16.1.1: + resolution: + { + integrity: sha512-dW7vl+yiAJSp6aCekaVnVJxurRv7DCOLyXqEG3RYMYUg7AuJ2jCqPkZTA8ooqC2vtnkaMcV5WfFBMuEnTu1OQg==, + } + engines: { node: ">=20" } + + globjoin@0.1.4: + resolution: + { + integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==, + } + + gopd@1.2.0: + resolution: + { + integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==, + } + engines: { node: ">= 0.4" } + + got@14.6.6: + resolution: + { + integrity: sha512-QLV1qeYSo5l13mQzWgP/y0LbMr5Plr5fJilgAIwgnwseproEbtNym8xpLsDzeZ6MWXgNE6kdWGBjdh3zT/Qerg==, + } + engines: { node: ">=20" } + + graceful-fs@4.2.10: + resolution: + { + integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==, + } + + graceful-fs@4.2.11: + resolution: + { + integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, + } + + handlebars@4.7.8: + resolution: + { + integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==, + } + engines: { node: ">=0.4.7" } + hasBin: true + + has-bigints@1.1.0: + resolution: + { + integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==, + } + engines: { node: ">= 0.4" } + + has-flag@3.0.0: + resolution: + { + integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==, + } + engines: { node: ">=4" } + + has-flag@4.0.0: + resolution: + { + integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, + } + engines: { node: ">=8" } + + has-flag@5.0.1: + resolution: + { + integrity: sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==, + } + engines: { node: ">=12" } + + has-property-descriptors@1.0.2: + resolution: + { + integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==, + } + + has-proto@1.2.0: + resolution: + { + integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==, + } + engines: { node: ">= 0.4" } + + has-symbols@1.1.0: + resolution: + { + integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==, + } + engines: { node: ">= 0.4" } + + has-tostringtag@1.0.2: + resolution: + { + integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==, + } + engines: { node: ">= 0.4" } + + hashery@1.5.0: + resolution: + { + integrity: sha512-nhQ6ExaOIqti2FDWoEMWARUqIKyjr2VcZzXShrI+A3zpeiuPWzx6iPftt44LhP74E5sW36B75N6VHbvRtpvO6Q==, + } + engines: { node: ">=20" } + + hasown@2.0.2: + resolution: + { + integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==, + } + engines: { node: ">= 0.4" } + + highlight.js@10.7.3: + resolution: + { + integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==, + } + + homedir-polyfill@1.0.3: + resolution: + { + integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==, + } + engines: { node: ">=0.10.0" } + + hook-std@4.0.0: + resolution: + { + integrity: sha512-IHI4bEVOt3vRUDJ+bFA9VUJlo7SzvFARPNLw75pqSmAOP2HmTWfFJtPvLBrDrlgjEYXY9zs7SFdHPQaJShkSCQ==, + } + engines: { node: ">=20" } + + hookified@1.15.1: + resolution: + { + integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==, + } + + hosted-git-info@7.0.2: + resolution: + { + integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + hosted-git-info@9.0.2: + resolution: + { + integrity: sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==, + } + engines: { node: ^20.17.0 || >=22.9.0 } + + hpagent@1.2.0: + resolution: + { + integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==, + } + engines: { node: ">=14" } + + html-tags@5.1.0: + resolution: + { + integrity: sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ==, + } + engines: { node: ">=20.10" } + + htmlfy@1.0.1: + resolution: + { + integrity: sha512-M85PmyEpWUDqhlEknsnvqmqGLq67h8e86WuKLMdXQdUl8iyvOSDbAAyv0tv0IVmIfh7Py1Vsot/IU1skdswt6g==, + } + + http-cache-semantics@4.2.0: + resolution: + { + integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==, + } + + http-proxy-agent@7.0.2: + resolution: + { + integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==, + } + engines: { node: ">= 14" } + + http2-wrapper@2.2.1: + resolution: + { + integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==, + } + engines: { node: ">=10.19.0" } + + https-proxy-agent@7.0.6: + resolution: + { + integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==, + } + engines: { node: ">= 14" } + + human-signals@2.1.0: + resolution: + { + integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==, + } + engines: { node: ">=10.17.0" } + + human-signals@5.0.0: + resolution: + { + integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==, + } + engines: { node: ">=16.17.0" } + + human-signals@8.0.1: + resolution: + { + integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==, + } + engines: { node: ">=18.18.0" } + + husky@9.1.7: + resolution: + { + integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==, + } + engines: { node: ">=18" } + hasBin: true + + iconv-lite@0.4.24: + resolution: + { + integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==, + } + engines: { node: ">=0.10.0" } + + iconv-lite@0.7.2: + resolution: + { + integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==, + } + engines: { node: ">=0.10.0" } + + idb@7.1.1: + resolution: + { + integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==, + } + + ieee754@1.2.1: + resolution: + { + integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==, + } + + ignore@5.3.2: + resolution: + { + integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, + } + engines: { node: ">= 4" } + + ignore@7.0.5: + resolution: + { + integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==, + } + engines: { node: ">= 4" } + + import-fresh@3.3.1: + resolution: + { + integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==, + } + engines: { node: ">=6" } + + import-from-esm@2.0.0: + resolution: + { + integrity: sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g==, + } + engines: { node: ">=18.20" } + + import-meta-resolve@4.2.0: + resolution: + { + integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==, + } + + imurmurhash@0.1.4: + resolution: + { + integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, + } + engines: { node: ">=0.8.19" } + + indent-string@4.0.0: + resolution: + { + integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==, + } + engines: { node: ">=8" } + + indent-string@5.0.0: + resolution: + { + integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==, + } + engines: { node: ">=12" } + + index-to-position@1.2.0: + resolution: + { + integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==, + } + engines: { node: ">=18" } + + inflight@1.0.6: + resolution: + { + integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==, + } + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: + { + integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==, + } + + ini@1.3.8: + resolution: + { + integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==, + } + + ini@4.1.1: + resolution: + { + integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + + inquirer@7.3.3: + resolution: + { + integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==, + } + engines: { node: ">=8.0.0" } + + inquirer@8.2.5: + resolution: + { + integrity: sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==, + } + engines: { node: ">=12.0.0" } + + internal-slot@1.1.0: + resolution: + { + integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==, + } + engines: { node: ">= 0.4" } + + internmap@2.0.3: + resolution: + { + integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==, + } + engines: { node: ">=12" } + + into-stream@7.0.0: + resolution: + { + integrity: sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==, + } + engines: { node: ">=12" } + + is-arguments@1.2.0: + resolution: + { + integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==, + } + engines: { node: ">= 0.4" } + + is-array-buffer@3.0.5: + resolution: + { + integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==, + } + engines: { node: ">= 0.4" } + + is-arrayish@0.2.1: + resolution: + { + integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==, + } + + is-async-function@2.1.1: + resolution: + { + integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==, + } + engines: { node: ">= 0.4" } + + is-bigint@1.1.0: + resolution: + { + integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==, + } + engines: { node: ">= 0.4" } + + is-binary-path@2.1.0: + resolution: + { + integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==, + } + engines: { node: ">=8" } + + is-boolean-object@1.2.2: + resolution: + { + integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==, + } + engines: { node: ">= 0.4" } + + is-callable@1.2.7: + resolution: + { + integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==, + } + engines: { node: ">= 0.4" } + + is-ci@4.1.0: + resolution: + { + integrity: sha512-Ab9bQDQ11lWootZUI5qxgN2ZXwxNI5hTwnsvOc1wyxQ7zQ8OkEDw79mI0+9jI3x432NfwbVRru+3noJfXF6lSQ==, + } + hasBin: true + + is-core-module@2.16.1: + resolution: + { + integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==, + } + engines: { node: ">= 0.4" } + + is-data-view@1.0.2: + resolution: + { + integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==, + } + engines: { node: ">= 0.4" } + + is-date-object@1.1.0: + resolution: + { + integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==, + } + engines: { node: ">= 0.4" } + + is-docker@3.0.0: + resolution: + { + integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + hasBin: true + + is-extglob@2.1.1: + resolution: + { + integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, + } + engines: { node: ">=0.10.0" } + + is-finalizationregistry@1.1.1: + resolution: + { + integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==, + } + engines: { node: ">= 0.4" } + + is-fullwidth-code-point@3.0.0: + resolution: + { + integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, + } + engines: { node: ">=8" } + + is-fullwidth-code-point@5.1.0: + resolution: + { + integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==, + } + engines: { node: ">=18" } + + is-generator-function@1.1.2: + resolution: + { + integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==, + } + engines: { node: ">= 0.4" } + + is-glob@4.0.3: + resolution: + { + integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, + } + engines: { node: ">=0.10.0" } + + is-inside-container@1.0.0: + resolution: + { + integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==, + } + engines: { node: ">=14.16" } + hasBin: true + + is-interactive@1.0.0: + resolution: + { + integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==, + } + engines: { node: ">=8" } + + is-map@2.0.3: + resolution: + { + integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==, + } + engines: { node: ">= 0.4" } + + is-module@1.0.0: + resolution: + { + integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==, + } + + is-negative-zero@2.0.3: + resolution: + { + integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==, + } + engines: { node: ">= 0.4" } + + is-number-object@1.1.1: + resolution: + { + integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==, + } + engines: { node: ">= 0.4" } + + is-number@7.0.0: + resolution: + { + integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, + } + engines: { node: ">=0.12.0" } + + is-obj@1.0.1: + resolution: + { + integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==, + } + engines: { node: ">=0.10.0" } + + is-obj@2.0.0: + resolution: + { + integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==, + } + engines: { node: ">=8" } + + is-path-inside@4.0.0: + resolution: + { + integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==, + } + engines: { node: ">=12" } + + is-plain-obj@4.1.0: + resolution: + { + integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==, + } + engines: { node: ">=12" } + + is-plain-object@5.0.0: + resolution: + { + integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==, + } + engines: { node: ">=0.10.0" } + + is-regex@1.2.1: + resolution: + { + integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==, + } + engines: { node: ">= 0.4" } + + is-regexp@1.0.0: + resolution: + { + integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==, + } + engines: { node: ">=0.10.0" } + + is-set@2.0.3: + resolution: + { + integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==, + } + engines: { node: ">= 0.4" } + + is-shared-array-buffer@1.0.4: + resolution: + { + integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==, + } + engines: { node: ">= 0.4" } + + is-stream@2.0.1: + resolution: + { + integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==, + } + engines: { node: ">=8" } + + is-stream@3.0.0: + resolution: + { + integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + is-stream@4.0.1: + resolution: + { + integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==, + } + engines: { node: ">=18" } + + is-string@1.1.1: + resolution: + { + integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==, + } + engines: { node: ">= 0.4" } + + is-symbol@1.1.1: + resolution: + { + integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==, + } + engines: { node: ">= 0.4" } + + is-typed-array@1.1.15: + resolution: + { + integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==, + } + engines: { node: ">= 0.4" } + + is-unicode-supported@0.1.0: + resolution: + { + integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==, + } + engines: { node: ">=10" } + + is-unicode-supported@2.1.0: + resolution: + { + integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==, + } + engines: { node: ">=18" } + + is-utf8@0.2.1: + resolution: + { + integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==, + } + + is-weakmap@2.0.2: + resolution: + { + integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==, + } + engines: { node: ">= 0.4" } + + is-weakref@1.1.1: + resolution: + { + integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==, + } + engines: { node: ">= 0.4" } + + is-weakset@2.0.4: + resolution: + { + integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==, + } + engines: { node: ">= 0.4" } + + is-windows@1.0.2: + resolution: + { + integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==, + } + engines: { node: ">=0.10.0" } + + is-wsl@3.1.1: + resolution: + { + integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==, + } + engines: { node: ">=16" } + + isarray@1.0.0: + resolution: + { + integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==, + } + + isarray@2.0.5: + resolution: + { + integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==, + } + + isexe@2.0.0: + resolution: + { + integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, + } + + issue-parser@7.0.1: + resolution: + { + integrity: sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==, + } + engines: { node: ^18.17 || >=20.6.1 } + + jackspeak@4.2.3: + resolution: + { + integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==, + } + engines: { node: 20 || >=22 } + + jake@10.9.4: + resolution: + { + integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==, + } + engines: { node: ">=10" } + hasBin: true + + java-properties@1.0.2: + resolution: + { + integrity: sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==, + } + engines: { node: ">= 0.6.0" } + + jiti@1.21.7: + resolution: + { + integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==, + } + hasBin: true + + jiti@2.6.1: + resolution: + { + integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==, + } + hasBin: true + + jpeg-exif@1.1.4: + resolution: + { + integrity: sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==, + } + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + js-tokens@4.0.0: + resolution: + { + integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, + } + + js-yaml@4.1.1: + resolution: + { + integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==, + } + hasBin: true + + jsesc@3.1.0: + resolution: + { + integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==, + } + engines: { node: ">=6" } + hasBin: true + + json-buffer@3.0.1: + resolution: + { + integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, + } + + json-fixer@1.6.15: + resolution: + { + integrity: sha512-TuDuZ5KrgyjoCIppdPXBMqiGfota55+odM+j2cQ5rt/XKyKmqGB3Whz1F8SN8+60yYGy/Nu5lbRZ+rx8kBIvBw==, + } + engines: { node: ">=10" } + + json-parse-better-errors@1.0.2: + resolution: + { + integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==, + } + + json-parse-even-better-errors@2.3.1: + resolution: + { + integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==, + } + + json-schema-traverse@0.4.1: + resolution: + { + integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, + } + + json-schema-traverse@1.0.0: + resolution: + { + integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==, + } + + json-schema@0.4.0: + resolution: + { + integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==, + } + + json-stable-stringify-without-jsonify@1.0.1: + resolution: + { + integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, + } + + json5@2.2.3: + resolution: + { + integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==, + } + engines: { node: ">=6" } + hasBin: true + + jsonfile@6.2.0: + resolution: + { + integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==, + } + + jsonpointer@5.0.1: + resolution: + { + integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==, + } + engines: { node: ">=0.10.0" } + + keyv@4.5.4: + resolution: + { + integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, + } + + keyv@5.6.0: + resolution: + { + integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==, + } + + kind-of@6.0.3: + resolution: + { + integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==, + } + engines: { node: ">=0.10.0" } + + known-css-properties@0.37.0: + resolution: + { + integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==, + } + + leaflet.markercluster@1.5.3: + resolution: + { + integrity: sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==, + } + peerDependencies: + leaflet: ^1.3.1 + + leaflet@1.9.4: + resolution: + { + integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==, + } + + leven@3.1.0: + resolution: + { + integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==, + } + engines: { node: ">=6" } + + levn@0.4.1: + resolution: + { + integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, + } + engines: { node: ">= 0.8.0" } + + lilconfig@3.1.3: + resolution: + { + integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==, + } + engines: { node: ">=14" } + + lines-and-columns@1.2.4: + resolution: + { + integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==, + } + + lint-staged@16.2.7: + resolution: + { + integrity: sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==, + } + engines: { node: ">=20.17" } + hasBin: true + + listr2@9.0.5: + resolution: + { + integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==, + } + engines: { node: ">=20.0.0" } + + lit-element@4.2.2: + resolution: + { + integrity: sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==, + } + + lit-html@3.3.2: + resolution: + { + integrity: sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==, + } + + lit@3.3.2: + resolution: + { + integrity: sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==, + } + + load-json-file@4.0.0: + resolution: + { + integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==, + } + engines: { node: ">=4" } + + locate-path@2.0.0: + resolution: + { + integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==, + } + engines: { node: ">=4" } + + locate-path@5.0.0: + resolution: + { + integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==, + } + engines: { node: ">=8" } + + locate-path@6.0.0: + resolution: + { + integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, + } + engines: { node: ">=10" } + + lodash-es@4.17.23: + resolution: + { + integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==, + } + + lodash.camelcase@4.3.0: + resolution: + { + integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==, + } + + lodash.capitalize@4.2.1: + resolution: + { + integrity: sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==, + } + + lodash.debounce@4.0.8: + resolution: + { + integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==, + } + + lodash.escaperegexp@4.1.2: + resolution: + { + integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==, + } + + lodash.isplainobject@4.0.6: + resolution: + { + integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==, + } + + lodash.isstring@4.0.1: + resolution: + { + integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==, + } + + lodash.kebabcase@4.1.1: + resolution: + { + integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==, + } + + lodash.map@4.6.0: + resolution: + { + integrity: sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==, + } + + lodash.memoize@4.1.2: + resolution: + { + integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==, + } + + lodash.mergewith@4.6.2: + resolution: + { + integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==, + } + + lodash.snakecase@4.1.1: + resolution: + { + integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==, + } + + lodash.sortby@4.7.0: + resolution: + { + integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==, + } + + lodash.startcase@4.4.0: + resolution: + { + integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==, + } + + lodash.truncate@4.4.2: + resolution: + { + integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==, + } + + lodash.uniq@4.5.0: + resolution: + { + integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==, + } + + lodash.uniqby@4.7.0: + resolution: + { + integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==, + } + + lodash.upperfirst@4.3.1: + resolution: + { + integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==, + } + + lodash@4.17.21: + resolution: + { + integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, + } + + lodash@4.17.23: + resolution: + { + integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==, + } + + log-symbols@4.1.0: + resolution: + { + integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==, + } + engines: { node: ">=10" } + + log-update@6.1.0: + resolution: + { + integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==, + } + engines: { node: ">=18" } + + longest@2.0.1: + resolution: + { + integrity: sha512-Ajzxb8CM6WAnFjgiloPsI3bF+WCxcvhdIG3KNA2KN962+tdBsHcuQ4k4qX/EcS/2CRkcc0iAkR956Nib6aXU/Q==, + } + engines: { node: ">=0.10.0" } + + lowercase-keys@3.0.0: + resolution: + { + integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + lru-cache@10.4.3: + resolution: + { + integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, + } + + lru-cache@11.2.6: + resolution: + { + integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==, + } + engines: { node: 20 || >=22 } + + lru-cache@5.1.1: + resolution: + { + integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, + } + + magic-string@0.25.9: + resolution: + { + integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==, + } + + make-asynchronous@1.0.1: + resolution: + { + integrity: sha512-T9BPOmEOhp6SmV25SwLVcHK4E6JyG/coH3C6F1NjNXSziv/fd4GmsqMk8YR6qpPOswfaOCApSNkZv6fxoaYFcQ==, + } + engines: { node: ">=18" } + + marked-terminal@7.3.0: + resolution: + { + integrity: sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==, + } + engines: { node: ">=16.0.0" } + peerDependencies: + marked: ">=1 <16" + + marked@15.0.12: + resolution: + { + integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==, + } + engines: { node: ">= 18" } + hasBin: true + + marked@17.0.3: + resolution: + { + integrity: sha512-jt1v2ObpyOKR8p4XaUJVk3YWRJ5n+i4+rjQopxvV32rSndTJXvIzuUdWWIy/1pFQMkQmvTXawzDNqOH/CUmx6A==, + } + engines: { node: ">= 20" } + hasBin: true + + math-intrinsics@1.1.0: + resolution: + { + integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==, + } + engines: { node: ">= 0.4" } + + mathml-tag-names@4.0.0: + resolution: + { + integrity: sha512-aa6AU2Pcx0VP/XWnh8IGL0SYSgQHDT6Ucror2j2mXeFAlN3ahaNs8EZtG1YiticMkSLj3Gt6VPFfZogt7G5iFQ==, + } + + mdn-data@2.0.28: + resolution: + { + integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==, + } + + mdn-data@2.12.2: + resolution: + { + integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==, + } + + meow@12.1.1: + resolution: + { + integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==, + } + engines: { node: ">=16.10" } + + meow@13.2.0: + resolution: + { + integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==, + } + engines: { node: ">=18" } + + meow@14.0.0: + resolution: + { + integrity: sha512-JhC3R1f6dbspVtmF3vKjAWz1EVIvwFrGGPLSdU6rK79xBwHWTuHoLnRX/t1/zHS1Ch1Y2UtIrih7DAHuH9JFJA==, + } + engines: { node: ">=20" } + + merge-stream@2.0.0: + resolution: + { + integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==, + } + + merge2@1.4.1: + resolution: + { + integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, + } + engines: { node: ">= 8" } + + merge@2.1.1: + resolution: + { + integrity: sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==, + } + + micromatch@4.0.8: + resolution: + { + integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==, + } + engines: { node: ">=8.6" } + + mime@4.1.0: + resolution: + { + integrity: sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==, + } + engines: { node: ">=16" } + hasBin: true + + mimic-fn@2.1.0: + resolution: + { + integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==, + } + engines: { node: ">=6" } + + mimic-fn@4.0.0: + resolution: + { + integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==, + } + engines: { node: ">=12" } + + mimic-function@5.0.1: + resolution: + { + integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==, + } + engines: { node: ">=18" } + + mimic-response@4.0.0: + resolution: + { + integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + mini-svg-data-uri@1.4.4: + resolution: + { + integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==, + } + hasBin: true + + minimatch@10.2.1: + resolution: + { + integrity: sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==, + } + engines: { node: 20 || >=22 } + + minimatch@3.1.2: + resolution: + { + integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, + } + + minimatch@5.1.6: + resolution: + { + integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==, + } + engines: { node: ">=10" } + + minimatch@9.0.5: + resolution: + { + integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==, + } + engines: { node: ">=16 || 14 >=14.17" } + + minimist@1.2.7: + resolution: + { + integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==, + } + + minimist@1.2.8: + resolution: + { + integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==, + } + + minipass@7.1.3: + resolution: + { + integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==, + } + engines: { node: ">=16 || 14 >=14.17" } + + mitt@3.0.1: + resolution: + { + integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==, + } + + mrmime@2.0.1: + resolution: + { + integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==, + } + engines: { node: ">=10" } + + ms@2.1.3: + resolution: + { + integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, + } + + mute-stream@0.0.8: + resolution: + { + integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==, + } + + mz@2.7.0: + resolution: + { + integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==, + } + + nano-spawn@2.0.0: + resolution: + { + integrity: sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==, + } + engines: { node: ">=20.17" } + + nanoid@3.3.11: + resolution: + { + integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==, + } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + hasBin: true + + natural-compare@1.4.0: + resolution: + { + integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, + } + + neo-async@2.6.2: + resolution: + { + integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==, + } + + nerf-dart@1.0.0: + resolution: + { + integrity: sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==, + } + + node-emoji@2.2.0: + resolution: + { + integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==, + } + engines: { node: ">=18" } + + node-fetch@2.7.0: + resolution: + { + integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==, + } + engines: { node: 4.x || >=6.0.0 } + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-releases@2.0.27: + resolution: + { + integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==, + } + + normalize-package-data@6.0.2: + resolution: + { + integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + normalize-package-data@8.0.0: + resolution: + { + integrity: sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==, + } + engines: { node: ^20.17.0 || >=22.9.0 } + + normalize-path@3.0.0: + resolution: + { + integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, + } + engines: { node: ">=0.10.0" } + + normalize-url@8.1.1: + resolution: + { + integrity: sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==, + } + engines: { node: ">=14.16" } + + npm-run-path@4.0.1: + resolution: + { + integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==, + } + engines: { node: ">=8" } + + npm-run-path@5.3.0: + resolution: + { + integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + npm-run-path@6.0.0: + resolution: + { + integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==, + } + engines: { node: ">=18" } + + npm@11.10.0: + resolution: + { + integrity: sha512-i8hE43iSIAMFuYVi8TxsEISdELM4fIza600aLjJ0ankGPLqd0oTPKMJqAcO/QWm307MbSlWGzJcNZ0lGMQgHPA==, + } + engines: { node: ^20.17.0 || >=22.9.0 } + hasBin: true + bundledDependencies: + - "@isaacs/string-locale-compare" + - "@npmcli/arborist" + - "@npmcli/config" + - "@npmcli/fs" + - "@npmcli/map-workspaces" + - "@npmcli/metavuln-calculator" + - "@npmcli/package-json" + - "@npmcli/promise-spawn" + - "@npmcli/redact" + - "@npmcli/run-script" + - "@sigstore/tuf" + - abbrev + - archy + - cacache + - chalk + - ci-info + - cli-columns + - fastest-levenshtein + - fs-minipass + - glob + - graceful-fs + - hosted-git-info + - ini + - init-package-json + - is-cidr + - json-parse-even-better-errors + - libnpmaccess + - libnpmdiff + - libnpmexec + - libnpmfund + - libnpmorg + - libnpmpack + - libnpmpublish + - libnpmsearch + - libnpmteam + - libnpmversion + - make-fetch-happen + - minimatch + - minipass + - minipass-pipeline + - ms + - node-gyp + - nopt + - npm-audit-report + - npm-install-checks + - npm-package-arg + - npm-pick-manifest + - npm-profile + - npm-registry-fetch + - npm-user-validate + - p-map + - pacote + - parse-conflict-json + - proc-log + - qrcode-terminal + - read + - semver + - spdx-expression-parse + - ssri + - supports-color + - tar + - text-table + - tiny-relative-date + - treeverse + - validate-npm-package-name + - which + + nth-check@2.1.1: + resolution: + { + integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==, + } + + object-assign@4.1.1: + resolution: + { + integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==, + } + engines: { node: ">=0.10.0" } + + object-hash@3.0.0: + resolution: + { + integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==, + } + engines: { node: ">= 6" } + + object-inspect@1.13.4: + resolution: + { + integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==, + } + engines: { node: ">= 0.4" } + + object-is@1.1.6: + resolution: + { + integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==, + } + engines: { node: ">= 0.4" } + + object-keys@1.1.1: + resolution: + { + integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==, + } + engines: { node: ">= 0.4" } + + object.assign@4.1.7: + resolution: + { + integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==, + } + engines: { node: ">= 0.4" } + + ohash@2.0.11: + resolution: + { + integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==, + } + + once@1.4.0: + resolution: + { + integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, + } + + onetime@5.1.2: + resolution: + { + integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==, + } + engines: { node: ">=6" } + + onetime@6.0.0: + resolution: + { + integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==, + } + engines: { node: ">=12" } + + onetime@7.0.0: + resolution: + { + integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==, + } + engines: { node: ">=18" } + + open@10.2.0: + resolution: + { + integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==, + } + engines: { node: ">=18" } + + optionator@0.9.4: + resolution: + { + integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==, + } + engines: { node: ">= 0.8.0" } + + ora@5.4.1: + resolution: + { + integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==, + } + engines: { node: ">=10" } + + os-tmpdir@1.0.2: + resolution: + { + integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==, + } + engines: { node: ">=0.10.0" } + + own-keys@1.0.1: + resolution: + { + integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==, + } + engines: { node: ">= 0.4" } + + p-cancelable@4.0.1: + resolution: + { + integrity: sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==, + } + engines: { node: ">=14.16" } + + p-each-series@3.0.0: + resolution: + { + integrity: sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==, + } + engines: { node: ">=12" } + + p-event@6.0.1: + resolution: + { + integrity: sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==, + } + engines: { node: ">=16.17" } + + p-filter@4.1.0: + resolution: + { + integrity: sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==, + } + engines: { node: ">=18" } + + p-is-promise@3.0.0: + resolution: + { + integrity: sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==, + } + engines: { node: ">=8" } + + p-limit@1.3.0: + resolution: + { + integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==, + } + engines: { node: ">=4" } + + p-limit@2.3.0: + resolution: + { + integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==, + } + engines: { node: ">=6" } + + p-limit@3.1.0: + resolution: + { + integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, + } + engines: { node: ">=10" } + + p-locate@2.0.0: + resolution: + { + integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==, + } + engines: { node: ">=4" } + + p-locate@4.1.0: + resolution: + { + integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==, + } + engines: { node: ">=8" } + + p-locate@5.0.0: + resolution: + { + integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==, + } + engines: { node: ">=10" } + + p-map@7.0.4: + resolution: + { + integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==, + } + engines: { node: ">=18" } + + p-reduce@2.1.0: + resolution: + { + integrity: sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==, + } + engines: { node: ">=8" } + + p-reduce@3.0.0: + resolution: + { + integrity: sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==, + } + engines: { node: ">=12" } + + p-timeout@6.1.4: + resolution: + { + integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==, + } + engines: { node: ">=14.16" } + + p-try@1.0.0: + resolution: + { + integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==, + } + engines: { node: ">=4" } + + p-try@2.2.0: + resolution: + { + integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==, + } + engines: { node: ">=6" } + + package-json-from-dist@1.0.1: + resolution: + { + integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==, + } + + pako@0.2.9: + resolution: + { + integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==, + } + + parent-module@1.0.1: + resolution: + { + integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, + } + engines: { node: ">=6" } + + parse-json@4.0.0: + resolution: + { + integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==, + } + engines: { node: ">=4" } + + parse-json@5.2.0: + resolution: + { + integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==, + } + engines: { node: ">=8" } + + parse-json@8.3.0: + resolution: + { + integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==, + } + engines: { node: ">=18" } + + parse-ms@4.0.0: + resolution: + { + integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==, + } + engines: { node: ">=18" } + + parse-passwd@1.0.0: + resolution: + { + integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==, + } + engines: { node: ">=0.10.0" } + + parse-path@7.1.0: + resolution: + { + integrity: sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==, + } + + parse-url@10.0.3: + resolution: + { + integrity: sha512-RvldzLvNE0DtOO1loukZsYcHCzHVUnHe7GNyrKIkavp7fNWs5ueX3kUzY/hXS8uRqDWwtaRKDcAmLEVouPIxIw==, + } + engines: { node: ">=14.13.0" } + + parse5-htmlparser2-tree-adapter@6.0.1: + resolution: + { + integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==, + } + + parse5@5.1.1: + resolution: + { + integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==, + } + + parse5@6.0.1: + resolution: + { + integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==, + } + + path-exists@3.0.0: + resolution: + { + integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==, + } + engines: { node: ">=4" } + + path-exists@4.0.0: + resolution: + { + integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, + } + engines: { node: ">=8" } + + path-is-absolute@1.0.1: + resolution: + { + integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==, + } + engines: { node: ">=0.10.0" } + + path-key@3.1.1: + resolution: + { + integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, + } + engines: { node: ">=8" } + + path-key@4.0.0: + resolution: + { + integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==, + } + engines: { node: ">=12" } + + path-parse@1.0.7: + resolution: + { + integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, + } + + path-scurry@2.0.1: + resolution: + { + integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==, + } + engines: { node: 20 || >=22 } + + path-type@4.0.0: + resolution: + { + integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==, + } + engines: { node: ">=8" } + + path-type@6.0.0: + resolution: + { + integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==, + } + engines: { node: ">=18" } + + pathe@2.0.3: + resolution: + { + integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, + } + + pdfmake@0.2.23: + resolution: + { + integrity: sha512-A/IksoKb/ikOZH1edSDJ/2zBbqJKDghD4+fXn3rT7quvCJDlsZMs3NmIB3eajLMMFU9Bd3bZPVvlUMXhvFI+bQ==, + } + engines: { node: ">=18" } + + pegjs@0.10.0: + resolution: + { + integrity: sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==, + } + engines: { node: ">=0.10" } + hasBin: true + + perfect-debounce@2.1.0: + resolution: + { + integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==, + } + + performance-now@2.1.0: + resolution: + { + integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==, + } + + picocolors@1.1.1: + resolution: + { + integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, + } + + picomatch@2.3.1: + resolution: + { + integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, + } + engines: { node: ">=8.6" } + + picomatch@4.0.3: + resolution: + { + integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==, + } + engines: { node: ">=12" } + + pidtree@0.6.0: + resolution: + { + integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==, + } + engines: { node: ">=0.10" } + hasBin: true + + pify@2.3.0: + resolution: + { + integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==, + } + engines: { node: ">=0.10.0" } + + pify@3.0.0: + resolution: + { + integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==, + } + engines: { node: ">=4" } + + pify@5.0.0: + resolution: + { + integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==, + } + engines: { node: ">=10" } + + pirates@4.0.7: + resolution: + { + integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==, + } + engines: { node: ">= 6" } + + pkg-conf@2.1.0: + resolution: + { + integrity: sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==, + } + engines: { node: ">=4" } + + png-js@1.0.0: + resolution: + { + integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==, + } + + polylabel@1.1.0: + resolution: + { + integrity: sha512-bxaGcA40sL3d6M4hH72Z4NdLqxpXRsCFk8AITYg6x1rn1Ei3izf00UMLklerBZTO49aPA3CYrIwVulx2Bce2pA==, + } + + possible-typed-array-names@1.1.0: + resolution: + { + integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==, + } + engines: { node: ">= 0.4" } + + postcss-attribute-case-insensitive@8.0.0: + resolution: + { + integrity: sha512-fovIPEV35c2JzVXdmP+sp2xirbBMt54J+upU8u6TSj410kUU5+axgEzvBBSAX8KCybze8CFCelzFAw/FfWg2TA==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-calc@10.1.1: + resolution: + { + integrity: sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==, + } + engines: { node: ^18.12 || ^20.9 || >=22.0 } + peerDependencies: + postcss: ^8.4.38 + + postcss-clamp@4.1.0: + resolution: + { + integrity: sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==, + } + engines: { node: ">=7.6.0" } + peerDependencies: + postcss: ^8.4.6 + + postcss-color-functional-notation@8.0.1: + resolution: + { + integrity: sha512-f1itLOG10iAa9mBAAtIHj/wfDs3srsNv/vrAsiRrIOfTCjhjxHxL1g06vvpQ86he2BP5HwB4cN72EZQ8rkegpA==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-color-hex-alpha@11.0.0: + resolution: + { + integrity: sha512-NCGa6vjIyrjosz9GqRxVKbONBklz5TeipYqTJp3IqbnBWlBq5e5EMtG6MaX4vqk9LzocPfMQkuRK9tfk+OQuKg==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-color-rebeccapurple@11.0.0: + resolution: + { + integrity: sha512-g9561mx7cbdqx7XeO/L+lJzVlzu7bICyXr72efBVKZGxIhvBBJf9fGXn3Cb6U4Bwh3LbzQO2e9NWBLVYdX5Eag==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-colormin@7.0.5: + resolution: + { + integrity: sha512-ekIBP/nwzRWhEMmIxHHbXHcMdzd1HIUzBECaj5KEdLz9DVP2HzT065sEhvOx1dkLjYW7jyD0CngThx6bpFi2fA==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-convert-values@7.0.8: + resolution: + { + integrity: sha512-+XNKuPfkHTCEo499VzLMYn94TiL3r9YqRE3Ty+jP7UX4qjewUONey1t7CG21lrlTLN07GtGM8MqFVp86D4uKJg==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-custom-media@12.0.0: + resolution: + { + integrity: sha512-jIgEvqceN6ru2uQ0f75W1g+JDi0UyECFeJKjPG7UcSkW3+03LDKH2c6h+9C0XuDTV4y2pEHmD5AJtVBq1OGnZA==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-custom-properties@15.0.0: + resolution: + { + integrity: sha512-FsD3VNtFr3qmspvIobDRszK9onKPHp8iHG4Aox2Nnm9SL93uw5GDw4z+NM7zWKiw6U+DSNm24JUm4coyIyanzQ==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-custom-selectors@9.0.0: + resolution: + { + integrity: sha512-VuV5tLPAm6wq1u699dsrhGCzfLobKe0eD3G8bw3BcTJt6wqQ7RQdfaveJVsCAi23OaQbjIi3K1C7Fj3yZH3f1g==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-dir-pseudo-class@10.0.0: + resolution: + { + integrity: sha512-DmtIzULpyC8XaH4b5AaUgt4Jic4QmrECqidNCdR7u7naQFdnxX80YI06u238a+ZVRXwURDxVzy0s/UQnWmpVeg==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-discard-comments@7.0.5: + resolution: + { + integrity: sha512-IR2Eja8WfYgN5n32vEGSctVQ1+JARfu4UH8M7bgGh1bC+xI/obsPJXaBpQF7MAByvgwZinhpHpdrmXtvVVlKcQ==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-discard-duplicates@7.0.2: + resolution: + { + integrity: sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-discard-empty@7.0.1: + resolution: + { + integrity: sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-discard-overridden@7.0.1: + resolution: + { + integrity: sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-double-position-gradients@7.0.0: + resolution: + { + integrity: sha512-Msr/dxj8Os7KLJE5Hdhvprwm3K5Zrh1KTY0eFN3ngPKNkej/Usy4BM9JQmqE6CLAkDpHoQVsi4snbL72CPt6qg==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-focus-visible@11.0.0: + resolution: + { + integrity: sha512-VG1a9kBKizUBWS66t5xyB4uLONBnvZLCmZXxT40FALu8EF0QgVZBYy5ApC0KhmpHsv+pvHMJHB3agKHwmocWjw==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-focus-within@10.0.0: + resolution: + { + integrity: sha512-dvql0fzUTG+gcJYp+KTbag5vAjuo94LDYZHkqDV1rnf5gPGer1v/SrmIZBdvKU8moep3HbcbujqGjzSb3DL53Q==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-font-variant@5.0.0: + resolution: + { + integrity: sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==, + } + peerDependencies: + postcss: ^8.1.0 + + postcss-gap-properties@7.0.0: + resolution: + { + integrity: sha512-PSDF2QoZMRUbsINvXObQgxx4HExRP85QTT8qS/YN9fBsCPWCqUuwqAD6E6PNp0BqL/jU1eyWUBORaOK/J/9LDA==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-image-set-function@8.0.0: + resolution: + { + integrity: sha512-rEGNkOkNusf4+IuMmfEoIdLuVmvbExGbmG+MIsyV6jR5UaWSoyPcAYHV/PxzVDCmudyF+2Nh/o6Ub2saqUdnuA==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-import@15.1.0: + resolution: + { + integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==, + } + engines: { node: ">=14.0.0" } + peerDependencies: + postcss: ^8.0.0 + + postcss-import@16.1.1: + resolution: + { + integrity: sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ==, + } + engines: { node: ">=18.0.0" } + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: + { + integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==, + } + engines: { node: ^12 || ^14 || >= 16 } + peerDependencies: + postcss: ^8.4.21 + + postcss-lab-function@8.0.1: + resolution: + { + integrity: sha512-Q/ANnuCYtanAc+2NnCaZrYu+GofYQUV603JXL0KB6GlcXxpnm/UerPAmpKQdb9pxYUkpKovGxfL43aOUnpF/Hg==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-load-config@6.0.1: + resolution: + { + integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==, + } + engines: { node: ">= 18" } + peerDependencies: + jiti: ">=1.21.0" + postcss: ">=8.0.9" + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-logical@9.0.0: + resolution: + { + integrity: sha512-A4LNd9dk3q/juEUA9Gd8ALhBO3TeOeYurnyHLlf2aAToD94VHR8c5Uv7KNmf8YVRhTxvWsyug4c5fKtARzyIRQ==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-merge-longhand@7.0.5: + resolution: + { + integrity: sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-merge-rules@7.0.7: + resolution: + { + integrity: sha512-njWJrd/Ms6XViwowaaCc+/vqhPG3SmXn725AGrnl+BgTuRPEacjiLEaGq16J6XirMJbtKkTwnt67SS+e2WGoew==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-minify-font-values@7.0.1: + resolution: + { + integrity: sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-minify-gradients@7.0.1: + resolution: + { + integrity: sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-minify-params@7.0.5: + resolution: + { + integrity: sha512-FGK9ky02h6Ighn3UihsyeAH5XmLEE2MSGH5Tc4tXMFtEDx7B+zTG6hD/+/cT+fbF7PbYojsmmWjyTwFwW1JKQQ==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-minify-selectors@7.0.5: + resolution: + { + integrity: sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-nested@6.2.0: + resolution: + { + integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==, + } + engines: { node: ">=12.0" } + peerDependencies: + postcss: ^8.2.14 + + postcss-nesting@14.0.0: + resolution: + { + integrity: sha512-YGFOfVrjxYfeGTS5XctP1WCI5hu8Lr9SmntjfRC+iX5hCihEO+QZl9Ra+pkjqkgoVdDKvb2JccpElcowhZtzpw==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-normalize-charset@7.0.1: + resolution: + { + integrity: sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-normalize-display-values@7.0.1: + resolution: + { + integrity: sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-normalize-positions@7.0.1: + resolution: + { + integrity: sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-normalize-repeat-style@7.0.1: + resolution: + { + integrity: sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-normalize-string@7.0.1: + resolution: + { + integrity: sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-normalize-timing-functions@7.0.1: + resolution: + { + integrity: sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-normalize-unicode@7.0.5: + resolution: + { + integrity: sha512-X6BBwiRxVaFHrb2WyBMddIeB5HBjJcAaUHyhLrM2FsxSq5TFqcHSsK7Zu1otag+o0ZphQGJewGH1tAyrD0zX1Q==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-normalize-url@7.0.1: + resolution: + { + integrity: sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-normalize-whitespace@7.0.1: + resolution: + { + integrity: sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-opacity-percentage@3.0.0: + resolution: + { + integrity: sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==, + } + engines: { node: ">=18" } + peerDependencies: + postcss: ^8.4 + + postcss-ordered-values@7.0.2: + resolution: + { + integrity: sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-overflow-shorthand@7.0.0: + resolution: + { + integrity: sha512-9SLpjoUdGRoRrzoOdX66HbUs0+uDwfIAiXsRa7piKGOqPd6F4ZlON9oaDSP5r1Qpgmzw5L9Ht0undIK6igJPMA==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-page-break@3.0.4: + resolution: + { + integrity: sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==, + } + peerDependencies: + postcss: ^8 + + postcss-place@11.0.0: + resolution: + { + integrity: sha512-fAifpyjQ+fuDRp2nmF95WbotqbpjdazebedahXdfBxy5sHembOLpBQ1cHveZD9ZmjK26tYM8tikeNaUlp/KfHA==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-preset-env@11.1.3: + resolution: + { + integrity: sha512-kZOfgzUc52yq2fJRZig7sHgWaHJoDOLABBoswe6TPTHgW3581VkP3eRj+Silhc7cJTomMjZZsyRHNjQ+bW11Gg==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-pseudo-class-any-link@11.0.0: + resolution: + { + integrity: sha512-DNFZ4GMa3C3pU5dM+UCTG1CEeLtS1ZqV5DKSqCTJQMn1G5jnd/30fS8+A7H4o5bSD3MOcnx+VgI+xPE9Z5Wvig==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-reduce-initial@7.0.5: + resolution: + { + integrity: sha512-RHagHLidG8hTZcnr4FpyMB2jtgd/OcyAazjMhoy5qmWJOx1uxKh4ntk0Pb46ajKM0rkf32lRH4C8c9qQiPR6IA==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-reduce-transforms@7.0.1: + resolution: + { + integrity: sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-replace-overflow-wrap@4.0.0: + resolution: + { + integrity: sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==, + } + peerDependencies: + postcss: ^8.0.3 + + postcss-reporter@7.1.0: + resolution: + { + integrity: sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA==, + } + engines: { node: ">=10" } + peerDependencies: + postcss: ^8.1.0 + + postcss-safe-parser@7.0.1: + resolution: + { + integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==, + } + engines: { node: ">=18.0" } + peerDependencies: + postcss: ^8.4.31 + + postcss-selector-not@9.0.0: + resolution: + { + integrity: sha512-xhAtTdHnVU2M/CrpYOPyRUvg3njhVlKmn2GNYXDaRJV9Ygx4d5OkSkc7NINzjUqnbDFtaKXlISOBeyMXU/zyFQ==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + postcss: ^8.4 + + postcss-selector-parser@6.0.10: + resolution: + { + integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==, + } + engines: { node: ">=4" } + + postcss-selector-parser@6.1.2: + resolution: + { + integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==, + } + engines: { node: ">=4" } + + postcss-selector-parser@7.1.1: + resolution: + { + integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==, + } + engines: { node: ">=4" } + + postcss-svgo@7.1.0: + resolution: + { + integrity: sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >= 18 } + peerDependencies: + postcss: ^8.4.32 + + postcss-unique-selectors@7.0.4: + resolution: + { + integrity: sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + postcss-value-parser@4.2.0: + resolution: + { + integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==, + } + + postcss@8.5.6: + resolution: + { + integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==, + } + engines: { node: ^10 || ^12 || >=14 } + + prelude-ls@1.2.1: + resolution: + { + integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, + } + engines: { node: ">= 0.8.0" } + + prettier-linter-helpers@1.0.1: + resolution: + { + integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==, + } + engines: { node: ">=6.0.0" } + + prettier-plugin-organize-imports@4.3.0: + resolution: + { + integrity: sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw==, + } + peerDependencies: + prettier: ">=2.0" + typescript: ">=2.9" + vue-tsc: ^2.1.0 || 3 + peerDependenciesMeta: + vue-tsc: + optional: true + + prettier@2.8.8: + resolution: + { + integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==, + } + engines: { node: ">=10.13.0" } + hasBin: true + + prettier@3.8.1: + resolution: + { + integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==, + } + engines: { node: ">=14" } + hasBin: true + + pretty-bytes@5.6.0: + resolution: + { + integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==, + } + engines: { node: ">=6" } + + pretty-bytes@6.1.1: + resolution: + { + integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==, + } + engines: { node: ^14.13.1 || >=16.0.0 } + + pretty-ms@9.3.0: + resolution: + { + integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==, + } + engines: { node: ">=18" } + + process-nextick-args@2.0.1: + resolution: + { + integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==, + } + + proto-list@1.2.4: + resolution: + { + integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==, + } + + protocols@2.0.2: + resolution: + { + integrity: sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==, + } + + punycode@2.3.1: + resolution: + { + integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, + } + engines: { node: ">=6" } + + qified@0.6.0: + resolution: + { + integrity: sha512-tsSGN1x3h569ZSU1u6diwhltLyfUWDp3YbFHedapTmpBl0B3P6U3+Qptg7xu+v+1io1EwhdPyyRHYbEw0KN2FA==, + } + engines: { node: ">=20" } + + queue-microtask@1.2.3: + resolution: + { + integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, + } + + quick-lru@5.1.1: + resolution: + { + integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==, + } + engines: { node: ">=10" } + + raf@3.4.1: + resolution: + { + integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==, + } + + randombytes@2.1.0: + resolution: + { + integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==, + } + + rc@1.2.8: + resolution: + { + integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==, + } + hasBin: true + + read-cache@1.0.0: + resolution: + { + integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==, + } + + read-package-up@11.0.0: + resolution: + { + integrity: sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==, + } + engines: { node: ">=18" } + + read-package-up@12.0.0: + resolution: + { + integrity: sha512-Q5hMVBYur/eQNWDdbF4/Wqqr9Bjvtrw2kjGxxBbKLbx8bVCL8gcArjTy8zDUuLGQicftpMuU0riQNcAsbtOVsw==, + } + engines: { node: ">=20" } + + read-pkg@10.1.0: + resolution: + { + integrity: sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==, + } + engines: { node: ">=20" } + + read-pkg@9.0.1: + resolution: + { + integrity: sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==, + } + engines: { node: ">=18" } + + readable-stream@2.3.8: + resolution: + { + integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==, + } + + readable-stream@3.6.2: + resolution: + { + integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==, + } + engines: { node: ">= 6" } + + readdirp@3.6.0: + resolution: + { + integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==, + } + engines: { node: ">=8.10.0" } + + reflect.getprototypeof@1.0.10: + resolution: + { + integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==, + } + engines: { node: ">= 0.4" } + + regenerate-unicode-properties@10.2.2: + resolution: + { + integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==, + } + engines: { node: ">=4" } + + regenerate@1.4.2: + resolution: + { + integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==, + } + + regexp.prototype.flags@1.5.4: + resolution: + { + integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==, + } + engines: { node: ">= 0.4" } + + regexpu-core@6.4.0: + resolution: + { + integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==, + } + engines: { node: ">=4" } + + registry-auth-token@5.1.1: + resolution: + { + integrity: sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==, + } + engines: { node: ">=14" } + + regjsgen@0.8.0: + resolution: + { + integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==, + } + + regjsparser@0.13.0: + resolution: + { + integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==, + } + hasBin: true + + regression@2.0.1: + resolution: + { + integrity: sha512-A4XYsc37dsBaNOgEjkJKzfJlE394IMmUPlI/p3TTI9u3T+2a+eox5Pr/CPUqF0eszeWZJPAc6QkroAhuUpWDJQ==, + } + + require-directory@2.1.1: + resolution: + { + integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==, + } + engines: { node: ">=0.10.0" } + + require-from-string@2.0.2: + resolution: + { + integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==, + } + engines: { node: ">=0.10.0" } + + require-main-filename@2.0.0: + resolution: + { + integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==, + } + + resolve-alpn@1.2.1: + resolution: + { + integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==, + } + + resolve-dir@1.0.1: + resolution: + { + integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==, + } + engines: { node: ">=0.10.0" } + + resolve-from@4.0.0: + resolution: + { + integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, + } + engines: { node: ">=4" } + + resolve-from@5.0.0: + resolution: + { + integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==, + } + engines: { node: ">=8" } + + resolve@1.22.11: + resolution: + { + integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==, + } + engines: { node: ">= 0.4" } + hasBin: true + + responselike@4.0.2: + resolution: + { + integrity: sha512-cGk8IbWEAnaCpdAt1BHzJ3Ahz5ewDJa0KseTsE3qIRMJ3C698W8psM7byCeWVpd/Ha7FUYzuRVzXoKoM6nRUbA==, + } + engines: { node: ">=20" } + + restore-cursor@3.1.0: + resolution: + { + integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==, + } + engines: { node: ">=8" } + + restore-cursor@5.1.0: + resolution: + { + integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==, + } + engines: { node: ">=18" } + + reusify@1.1.0: + resolution: + { + integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==, + } + engines: { iojs: ">=1.0.0", node: ">=0.10.0" } + + rfdc@1.4.1: + resolution: + { + integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==, + } + + rgbcolor@1.0.1: + resolution: + { + integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==, + } + engines: { node: ">= 0.8.15" } + + rollup@2.79.2: + resolution: + { + integrity: sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==, + } + engines: { node: ">=10.0.0" } + hasBin: true + + rollup@4.57.1: + resolution: + { + integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==, + } + engines: { node: ">=18.0.0", npm: ">=8.0.0" } + hasBin: true + + run-applescript@7.1.0: + resolution: + { + integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==, + } + engines: { node: ">=18" } + + run-async@2.4.1: + resolution: + { + integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==, + } + engines: { node: ">=0.12.0" } + + run-parallel@1.2.0: + resolution: + { + integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, + } + + rxjs@6.6.7: + resolution: + { + integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==, + } + engines: { npm: ">=2.0.0" } + + rxjs@7.8.2: + resolution: + { + integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==, + } + + safe-array-concat@1.1.3: + resolution: + { + integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==, + } + engines: { node: ">=0.4" } + + safe-buffer@5.1.2: + resolution: + { + integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==, + } + + safe-buffer@5.2.1: + resolution: + { + integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==, + } + + safe-push-apply@1.0.0: + resolution: + { + integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==, + } + engines: { node: ">= 0.4" } + + safe-regex-test@1.1.0: + resolution: + { + integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==, + } + engines: { node: ">= 0.4" } + + safer-buffer@2.1.2: + resolution: + { + integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, + } + + sax@1.4.4: + resolution: + { + integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==, + } + engines: { node: ">=11.0.0" } + + semantic-release@25.0.3: + resolution: + { + integrity: sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA==, + } + engines: { node: ^22.14.0 || >= 24.10.0 } + hasBin: true + + semver-regex@4.0.5: + resolution: + { + integrity: sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==, + } + engines: { node: ">=12" } + + semver@6.3.1: + resolution: + { + integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==, + } + hasBin: true + + semver@7.7.4: + resolution: + { + integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==, + } + engines: { node: ">=10" } + hasBin: true + + serialize-javascript@6.0.2: + resolution: + { + integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==, + } + + set-blocking@2.0.0: + resolution: + { + integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==, + } + + set-function-length@1.2.2: + resolution: + { + integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==, + } + engines: { node: ">= 0.4" } + + set-function-name@2.0.2: + resolution: + { + integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==, + } + engines: { node: ">= 0.4" } + + set-proto@1.0.0: + resolution: + { + integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==, + } + engines: { node: ">= 0.4" } + + sharp@0.34.5: + resolution: + { + integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + + shebang-command@2.0.0: + resolution: + { + integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, + } + engines: { node: ">=8" } + + shebang-regex@3.0.0: + resolution: + { + integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, + } + engines: { node: ">=8" } + + side-channel-list@1.0.0: + resolution: + { + integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==, + } + engines: { node: ">= 0.4" } + + side-channel-map@1.0.1: + resolution: + { + integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==, + } + engines: { node: ">= 0.4" } + + side-channel-weakmap@1.0.2: + resolution: + { + integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==, + } + engines: { node: ">= 0.4" } + + side-channel@1.1.0: + resolution: + { + integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==, + } + engines: { node: ">= 0.4" } + + signal-exit@3.0.7: + resolution: + { + integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, + } + + signal-exit@4.1.0: + resolution: + { + integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==, + } + engines: { node: ">=14" } + + signale@1.4.0: + resolution: + { + integrity: sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==, + } + engines: { node: ">=6" } + + sirv@3.0.2: + resolution: + { + integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==, + } + engines: { node: ">=18" } + + skin-tone@2.0.0: + resolution: + { + integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==, + } + engines: { node: ">=8" } + + slash@5.1.0: + resolution: + { + integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==, + } + engines: { node: ">=14.16" } + + slice-ansi@4.0.0: + resolution: + { + integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==, + } + engines: { node: ">=10" } + + slice-ansi@7.1.2: + resolution: + { + integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==, + } + engines: { node: ">=18" } + + smob@1.6.1: + resolution: + { + integrity: sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==, + } + engines: { node: ">=20.0.0" } + + source-map-js@1.2.1: + resolution: + { + integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, + } + engines: { node: ">=0.10.0" } + + source-map-support@0.5.21: + resolution: + { + integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==, + } + + source-map@0.6.1: + resolution: + { + integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, + } + engines: { node: ">=0.10.0" } + + source-map@0.8.0-beta.0: + resolution: + { + integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==, + } + engines: { node: ">= 8" } + deprecated: The work that was done in this beta branch won't be included in future versions + + sourcemap-codec@1.4.8: + resolution: + { + integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==, + } + deprecated: Please use @jridgewell/sourcemap-codec instead + + spawn-error-forwarder@1.0.0: + resolution: + { + integrity: sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==, + } + + spdx-correct@3.2.0: + resolution: + { + integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==, + } + + spdx-exceptions@2.5.0: + resolution: + { + integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==, + } + + spdx-expression-parse@3.0.1: + resolution: + { + integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==, + } + + spdx-license-ids@3.0.22: + resolution: + { + integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==, + } + + split2@1.0.0: + resolution: + { + integrity: sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==, + } + + split2@4.2.0: + resolution: + { + integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==, + } + engines: { node: ">= 10.x" } + + stackblur-canvas@2.7.0: + resolution: + { + integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==, + } + engines: { node: ">=0.1.14" } + + stencil-wormhole@3.4.1: + resolution: + { + integrity: sha512-ppYTcWTJnIl4ZAKwF39LTA9f/ypHfbVefsHdN2hpMQGrR57wt1TieZo9tlCM/r1Y4SFiZ5yz/cjho564C921Xw==, + } + + stop-iteration-iterator@1.1.0: + resolution: + { + integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==, + } + engines: { node: ">= 0.4" } + + stream-combiner2@1.1.1: + resolution: + { + integrity: sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==, + } + + string-argv@0.3.2: + resolution: + { + integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==, + } + engines: { node: ">=0.6.19" } + + string-width@4.2.3: + resolution: + { + integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, + } + engines: { node: ">=8" } + + string-width@7.2.0: + resolution: + { + integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==, + } + engines: { node: ">=18" } + + string-width@8.2.0: + resolution: + { + integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==, + } + engines: { node: ">=20" } + + string.prototype.matchall@4.0.12: + resolution: + { + integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==, + } + engines: { node: ">= 0.4" } + + string.prototype.trim@1.2.10: + resolution: + { + integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==, + } + engines: { node: ">= 0.4" } + + string.prototype.trimend@1.0.9: + resolution: + { + integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==, + } + engines: { node: ">= 0.4" } + + string.prototype.trimstart@1.0.8: + resolution: + { + integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==, + } + engines: { node: ">= 0.4" } + + string_decoder@1.1.1: + resolution: + { + integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==, + } + + string_decoder@1.3.0: + resolution: + { + integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==, + } + + stringify-object@3.3.0: + resolution: + { + integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==, + } + engines: { node: ">=4" } + + strip-ansi@6.0.1: + resolution: + { + integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, + } + engines: { node: ">=8" } + + strip-ansi@7.1.2: + resolution: + { + integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==, + } + engines: { node: ">=12" } + + strip-bom@3.0.0: + resolution: + { + integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==, + } + engines: { node: ">=4" } + + strip-bom@4.0.0: + resolution: + { + integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==, + } + engines: { node: ">=8" } + + strip-comments@2.0.1: + resolution: + { + integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==, + } + engines: { node: ">=10" } + + strip-final-newline@2.0.0: + resolution: + { + integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==, + } + engines: { node: ">=6" } + + strip-final-newline@3.0.0: + resolution: + { + integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==, + } + engines: { node: ">=12" } + + strip-final-newline@4.0.0: + resolution: + { + integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==, + } + engines: { node: ">=18" } + + strip-json-comments@2.0.1: + resolution: + { + integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==, + } + engines: { node: ">=0.10.0" } + + strip-json-comments@3.1.1: + resolution: + { + integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, + } + engines: { node: ">=8" } + + style-mod@4.1.3: + resolution: + { + integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==, + } + + stylehacks@7.0.7: + resolution: + { + integrity: sha512-bJkD0JkEtbRrMFtwgpJyBbFIwfDDONQ1Ov3sDLZQP8HuJ73kBOyx66H4bOcAbVWmnfLdvQ0AJwXxOMkpujcO6g==, + } + engines: { node: ^18.12.0 || ^20.9.0 || >=22.0 } + peerDependencies: + postcss: ^8.4.32 + + stylelint-config-recommended@18.0.0: + resolution: + { + integrity: sha512-mxgT2XY6YZ3HWWe3Di8umG6aBmWmHTblTgu/f10rqFXnyWxjKWwNdjSWkgkwCtxIKnqjSJzvFmPT5yabVIRxZg==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + stylelint: ^17.0.0 + + stylelint-config-standard@40.0.0: + resolution: + { + integrity: sha512-EznGJxOUhtWck2r6dJpbgAdPATIzvpLdK9+i5qPd4Lx70es66TkBPljSg4wN3Qnc6c4h2n+WbUrUynQ3fanjHw==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + stylelint: ^17.0.0 + + stylelint@17.3.0: + resolution: + { + integrity: sha512-1POV91lcEMhj6SLVaOeA0KlS9yattS+qq+cyWqP/nYzWco7K5jznpGH1ExngvPlTM9QF1Kjd2bmuzJu9TH2OcA==, + } + engines: { node: ">=20.19.0" } + hasBin: true + + sucrase@3.35.1: + resolution: + { + integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==, + } + engines: { node: ">=16 || 14 >=14.17" } + hasBin: true + + super-regex@1.1.0: + resolution: + { + integrity: sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==, + } + engines: { node: ">=18" } + + supports-color@10.2.2: + resolution: + { + integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==, + } + engines: { node: ">=18" } + + supports-color@5.5.0: + resolution: + { + integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==, + } + engines: { node: ">=4" } + + supports-color@7.2.0: + resolution: + { + integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, + } + engines: { node: ">=8" } + + supports-hyperlinks@3.2.0: + resolution: + { + integrity: sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==, + } + engines: { node: ">=14.18" } + + supports-hyperlinks@4.4.0: + resolution: + { + integrity: sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg==, + } + engines: { node: ">=20" } + + supports-preserve-symlinks-flag@1.0.0: + resolution: + { + integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, + } + engines: { node: ">= 0.4" } + + svg-tags@1.0.0: + resolution: + { + integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==, + } + + svgo@4.0.0: + resolution: + { + integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==, + } + engines: { node: ">=16" } + hasBin: true + + synckit@0.11.12: + resolution: + { + integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==, + } + engines: { node: ^14.18.0 || >=16.0.0 } + + table@6.9.0: + resolution: + { + integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==, + } + engines: { node: ">=10.0.0" } + + tagged-tag@1.0.0: + resolution: + { + integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==, + } + engines: { node: ">=20" } + + tailwindcss@3.4.19: + resolution: + { + integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==, + } + engines: { node: ">=14.0.0" } + hasBin: true + + temp-dir@2.0.0: + resolution: + { + integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==, + } + engines: { node: ">=8" } + + temp-dir@3.0.0: + resolution: + { + integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==, + } + engines: { node: ">=14.16" } + + tempy@0.6.0: + resolution: + { + integrity: sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==, + } + engines: { node: ">=10" } + + tempy@3.2.0: + resolution: + { + integrity: sha512-d79HhZya5Djd7am0q+W4RTsSU+D/aJzM+4Y4AGJGuGlgM2L6sx5ZvOYTmZjqPhrDrV6xJTtRSm1JCLj6V6LHLQ==, + } + engines: { node: ">=14.16" } + + terser@5.46.0: + resolution: + { + integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==, + } + engines: { node: ">=10" } + hasBin: true + + thenby@1.3.4: + resolution: + { + integrity: sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==, + } + + thenify-all@1.6.0: + resolution: + { + integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==, + } + engines: { node: ">=0.8" } + + thenify@3.3.1: + resolution: + { + integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==, + } + + through2@2.0.5: + resolution: + { + integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==, + } + + through@2.3.8: + resolution: + { + integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==, + } + + time-span@5.1.0: + resolution: + { + integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==, + } + engines: { node: ">=12" } + + tiny-inflate@1.0.3: + resolution: + { + integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==, + } + + tinyexec@1.0.2: + resolution: + { + integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==, + } + engines: { node: ">=18" } + + tinyglobby@0.2.15: + resolution: + { + integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==, + } + engines: { node: ">=12.0.0" } + + tinyqueue@2.0.3: + resolution: + { + integrity: sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==, + } + + tmp@0.0.33: + resolution: + { + integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==, + } + engines: { node: ">=0.6.0" } + + to-regex-range@5.0.1: + resolution: + { + integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, + } + engines: { node: ">=8.0" } + + totalist@3.0.1: + resolution: + { + integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==, + } + engines: { node: ">=6" } + + tr46@0.0.3: + resolution: + { + integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==, + } + + tr46@1.0.1: + resolution: + { + integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==, + } + + traverse@0.6.8: + resolution: + { + integrity: sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==, + } + engines: { node: ">= 0.4" } + + ts-api-utils@2.4.0: + resolution: + { + integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==, + } + engines: { node: ">=18.12" } + peerDependencies: + typescript: ">=4.8.4" + + ts-interface-checker@0.1.13: + resolution: + { + integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==, + } + + tslib@1.14.1: + resolution: + { + integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==, + } + + tslib@2.8.1: + resolution: + { + integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, + } + + tunnel@0.0.6: + resolution: + { + integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==, + } + engines: { node: ">=0.6.11 <=0.7.0 || >=0.7.3" } + + type-check@0.4.0: + resolution: + { + integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, + } + engines: { node: ">= 0.8.0" } + + type-fest@0.16.0: + resolution: + { + integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==, + } + engines: { node: ">=10" } + + type-fest@0.21.3: + resolution: + { + integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==, + } + engines: { node: ">=10" } + + type-fest@1.4.0: + resolution: + { + integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==, + } + engines: { node: ">=10" } + + type-fest@2.19.0: + resolution: + { + integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==, + } + engines: { node: ">=12.20" } + + type-fest@4.41.0: + resolution: + { + integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==, + } + engines: { node: ">=16" } + + type-fest@5.4.4: + resolution: + { + integrity: sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==, + } + engines: { node: ">=20" } + + typed-array-buffer@1.0.3: + resolution: + { + integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==, + } + engines: { node: ">= 0.4" } + + typed-array-byte-length@1.0.3: + resolution: + { + integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==, + } + engines: { node: ">= 0.4" } + + typed-array-byte-offset@1.0.4: + resolution: + { + integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==, + } + engines: { node: ">= 0.4" } + + typed-array-length@1.0.7: + resolution: + { + integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==, + } + engines: { node: ">= 0.4" } + + typescript-eslint@8.56.0: + resolution: + { + integrity: sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.0.0" + + typescript@5.9.3: + resolution: + { + integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==, + } + engines: { node: ">=14.17" } + hasBin: true + + uglify-js@3.19.3: + resolution: + { + integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==, + } + engines: { node: ">=0.8.0" } + hasBin: true + + unbox-primitive@1.1.0: + resolution: + { + integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==, + } + engines: { node: ">= 0.4" } + + undici-types@7.14.0: + resolution: + { + integrity: sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==, + } + + undici@6.23.0: + resolution: + { + integrity: sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==, + } + engines: { node: ">=18.17" } + + undici@7.22.0: + resolution: + { + integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==, + } + engines: { node: ">=20.18.1" } + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: + { + integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==, + } + engines: { node: ">=4" } + + unicode-emoji-modifier-base@1.0.0: + resolution: + { + integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==, + } + engines: { node: ">=4" } + + unicode-match-property-ecmascript@2.0.0: + resolution: + { + integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==, + } + engines: { node: ">=4" } + + unicode-match-property-value-ecmascript@2.2.1: + resolution: + { + integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==, + } + engines: { node: ">=4" } + + unicode-properties@1.4.1: + resolution: + { + integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==, + } + + unicode-property-aliases-ecmascript@2.2.0: + resolution: + { + integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==, + } + engines: { node: ">=4" } + + unicode-trie@2.0.0: + resolution: + { + integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==, + } + + unicorn-magic@0.1.0: + resolution: + { + integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==, + } + engines: { node: ">=18" } + + unicorn-magic@0.3.0: + resolution: + { + integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==, + } + engines: { node: ">=18" } + + unicorn-magic@0.4.0: + resolution: + { + integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==, + } + engines: { node: ">=20" } + + unique-string@2.0.0: + resolution: + { + integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==, + } + engines: { node: ">=8" } + + unique-string@3.0.0: + resolution: + { + integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==, + } + engines: { node: ">=12" } + + universal-user-agent@7.0.3: + resolution: + { + integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==, + } + + universalify@2.0.1: + resolution: + { + integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==, + } + engines: { node: ">= 10.0.0" } + + unplugin-utils@0.3.1: + resolution: + { + integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==, + } + engines: { node: ">=20.19.0" } + + upath@1.2.0: + resolution: + { + integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==, + } + engines: { node: ">=4" } + + update-browserslist-db@1.2.3: + resolution: + { + integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==, + } + hasBin: true + peerDependencies: + browserslist: ">= 4.21.0" + + uri-js@4.4.1: + resolution: + { + integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, + } + + url-join@4.0.1: + resolution: + { + integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==, + } + + url-join@5.0.0: + resolution: + { + integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + util-deprecate@1.0.2: + resolution: + { + integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, + } + + validate-npm-package-license@3.0.4: + resolution: + { + integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==, + } + + vite-dev-rpc@1.1.0: + resolution: + { + integrity: sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==, + } + peerDependencies: + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 + + vite-hot-client@2.1.0: + resolution: + { + integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==, + } + peerDependencies: + vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 + + vite-plugin-codeigniter@2.0.0: + resolution: + { + integrity: sha512-F4S23BlXu1Yhb2k/cHeB3DV8q1BN+GNFf63jDFRqL4vwdv12/RWK+hBhNSsUUQguklogluza6GL+U1W8t/ba0w==, + } + peerDependencies: + vite: ^7.0.0 + + vite-plugin-inspect@11.3.3: + resolution: + { + integrity: sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==, + } + engines: { node: ">=14" } + peerDependencies: + "@nuxt/kit": "*" + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + "@nuxt/kit": + optional: true + + vite-plugin-pwa@1.2.0: + resolution: + { + integrity: sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw==, + } + engines: { node: ">=16.0.0" } + peerDependencies: + "@vite-pwa/assets-generator": ^1.0.0 + vite: ^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + workbox-build: ^7.4.0 + workbox-window: ^7.4.0 + peerDependenciesMeta: + "@vite-pwa/assets-generator": + optional: true + + vite-plugin-static-copy@3.2.0: + resolution: + { + integrity: sha512-g2k9z8B/1Bx7D4wnFjPLx9dyYGrqWMLTpwTtPHhcU+ElNZP2O4+4OsyaficiDClus0dzVhdGvoGFYMJxoXZ12Q==, + } + engines: { node: ^18.0.0 || >=20.0.0 } + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + + vite@7.3.1: + resolution: + { + integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + hasBin: true + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + jiti: ">=1.21.0" + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + w3c-keyname@2.2.8: + resolution: + { + integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==, + } + + wavesurfer.js@7.12.1: + resolution: + { + integrity: sha512-NswPjVHxk0Q1F/VMRemCPUzSojjuHHisQrBqQiRXg7MVbe3f5vQ6r0rTTXA/a/neC/4hnOEC4YpXca4LpH0SUg==, + } + + wcwidth@1.0.1: + resolution: + { + integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==, + } + + web-worker@1.2.0: + resolution: + { + integrity: sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==, + } + + webidl-conversions@3.0.1: + resolution: + { + integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==, + } + + webidl-conversions@4.0.2: + resolution: + { + integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==, + } + + whatwg-url@5.0.0: + resolution: + { + integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==, + } + + whatwg-url@7.1.0: + resolution: + { + integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==, + } + + which-boxed-primitive@1.1.1: + resolution: + { + integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==, + } + engines: { node: ">= 0.4" } + + which-builtin-type@1.2.1: + resolution: + { + integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==, + } + engines: { node: ">= 0.4" } + + which-collection@1.0.2: + resolution: + { + integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==, + } + engines: { node: ">= 0.4" } + + which-module@2.0.1: + resolution: + { + integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==, + } + + which-typed-array@1.1.20: + resolution: + { + integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==, + } + engines: { node: ">= 0.4" } + + which@1.3.1: + resolution: + { + integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==, + } + hasBin: true + + which@2.0.2: + resolution: + { + integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, + } + engines: { node: ">= 8" } + hasBin: true + + word-wrap@1.2.5: + resolution: + { + integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==, + } + engines: { node: ">=0.10.0" } + + wordwrap@1.0.0: + resolution: + { + integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==, + } + + workbox-background-sync@7.4.0: + resolution: + { + integrity: sha512-8CB9OxKAgKZKyNMwfGZ1XESx89GryWTfI+V5yEj8sHjFH8MFelUwYXEyldEK6M6oKMmn807GoJFUEA1sC4XS9w==, + } + + workbox-broadcast-update@7.4.0: + resolution: + { + integrity: sha512-+eZQwoktlvo62cI0b+QBr40v5XjighxPq3Fzo9AWMiAosmpG5gxRHgTbGGhaJv/q/MFVxwFNGh/UwHZ/8K88lA==, + } + + workbox-build@7.4.0: + resolution: + { + integrity: sha512-Ntk1pWb0caOFIvwz/hfgrov/OJ45wPEhI5PbTywQcYjyZiVhT3UrwwUPl6TRYbTm4moaFYithYnl1lvZ8UjxcA==, + } + engines: { node: ">=20.0.0" } + + workbox-cacheable-response@7.4.0: + resolution: + { + integrity: sha512-0Fb8795zg/x23ISFkAc7lbWes6vbw34DGFIMw31cwuHPgDEC/5EYm6m/ZkylLX0EnEbbOyOCLjKgFS/Z5g0HeQ==, + } + + workbox-core@7.4.0: + resolution: + { + integrity: sha512-6BMfd8tYEnN4baG4emG9U0hdXM4gGuDU3ectXuVHnj71vwxTFI7WOpQJC4siTOlVtGqCUtj0ZQNsrvi6kZZTAQ==, + } + + workbox-expiration@7.4.0: + resolution: + { + integrity: sha512-V50p4BxYhtA80eOvulu8xVfPBgZbkxJ1Jr8UUn0rvqjGhLDqKNtfrDfjJKnLz2U8fO2xGQJTx/SKXNTzHOjnHw==, + } + + workbox-google-analytics@7.4.0: + resolution: + { + integrity: sha512-MVPXQslRF6YHkzGoFw1A4GIB8GrKym/A5+jYDUSL+AeJw4ytQGrozYdiZqUW1TPQHW8isBCBtyFJergUXyNoWQ==, + } + + workbox-navigation-preload@7.4.0: + resolution: + { + integrity: sha512-etzftSgdQfjMcfPgbfaZCfM2QuR1P+4o8uCA2s4rf3chtKTq/Om7g/qvEOcZkG6v7JZOSOxVYQiOu6PbAZgU6w==, + } + + workbox-precaching@7.4.0: + resolution: + { + integrity: sha512-VQs37T6jDqf1rTxUJZXRl3yjZMf5JX/vDPhmx2CPgDDKXATzEoqyRqhYnRoxl6Kr0rqaQlp32i9rtG5zTzIlNg==, + } + + workbox-range-requests@7.4.0: + resolution: + { + integrity: sha512-3Vq854ZNuP6Y0KZOQWLaLC9FfM7ZaE+iuQl4VhADXybwzr4z/sMmnLgTeUZLq5PaDlcJBxYXQ3U91V7dwAIfvw==, + } + + workbox-recipes@7.4.0: + resolution: + { + integrity: sha512-kOkWvsAn4H8GvAkwfJTbwINdv4voFoiE9hbezgB1sb/0NLyTG4rE7l6LvS8lLk5QIRIto+DjXLuAuG3Vmt3cxQ==, + } + + workbox-routing@7.4.0: + resolution: + { + integrity: sha512-C/ooj5uBWYAhAqwmU8HYQJdOjjDKBp9MzTQ+otpMmd+q0eF59K+NuXUek34wbL0RFrIXe/KKT+tUWcZcBqxbHQ==, + } + + workbox-strategies@7.4.0: + resolution: + { + integrity: sha512-T4hVqIi5A4mHi92+5EppMX3cLaVywDp8nsyUgJhOZxcfSV/eQofcOA6/EMo5rnTNmNTpw0rUgjAI6LaVullPpg==, + } + + workbox-streams@7.4.0: + resolution: + { + integrity: sha512-QHPBQrey7hQbnTs5GrEVoWz7RhHJXnPT+12qqWM378orDMo5VMJLCkCM1cnCk+8Eq92lccx/VgRZ7WAzZWbSLg==, + } + + workbox-sw@7.4.0: + resolution: + { + integrity: sha512-ltU+Kr3qWR6BtbdlMnCjobZKzeV1hN+S6UvDywBrwM19TTyqA03X66dzw1tEIdJvQ4lYKkBFox6IAEhoSEZ8Xw==, + } + + workbox-window@7.4.0: + resolution: + { + integrity: sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw==, + } + + wrap-ansi@6.2.0: + resolution: + { + integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==, + } + engines: { node: ">=8" } + + wrap-ansi@7.0.0: + resolution: + { + integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==, + } + engines: { node: ">=10" } + + wrap-ansi@9.0.2: + resolution: + { + integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==, + } + engines: { node: ">=18" } + + wrappy@1.0.2: + resolution: + { + integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, + } + + write-file-atomic@7.0.0: + resolution: + { + integrity: sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg==, + } + engines: { node: ^20.17.0 || >=22.9.0 } + + wsl-utils@0.1.0: + resolution: + { + integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==, + } + engines: { node: ">=18" } + + xml-formatter@3.6.7: + resolution: + { + integrity: sha512-IsfFYJQuoDqtUlKhm4EzeoBOb+fQwzQVeyxxAQ0sThn/nFnQmyLPTplqq4yRhaOENH/tAyujD2TBfIYzUKB6hg==, + } + engines: { node: ">= 16" } + + xml-parser-xo@4.1.5: + resolution: + { + integrity: sha512-TxyRxk9sTOUg3glxSIY6f0nfuqRll2OEF8TspLgh5mZkLuBgheCn3zClcDSGJ58TvNmiwyCCuat4UajPud/5Og==, + } + engines: { node: ">= 16" } + + xmldoc@2.0.3: + resolution: + { + integrity: sha512-6gRk4NY/Jvg67xn7OzJuxLRsGgiXBaPUQplVJ/9l99uIugxh4FTOewYz5ic8WScj7Xx/2WvhENiQKwkK9RpE4w==, + } + engines: { node: ">=12.0.0" } + + xtend@4.0.2: + resolution: + { + integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==, + } + engines: { node: ">=0.4" } + + y18n@4.0.3: + resolution: + { + integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==, + } + + y18n@5.0.8: + resolution: + { + integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==, + } + engines: { node: ">=10" } + + yallist@3.1.1: + resolution: + { + integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, + } + + yaml@2.8.2: + resolution: + { + integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==, + } + engines: { node: ">= 14.6" } + hasBin: true + + yargs-parser@18.1.3: + resolution: + { + integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==, + } + engines: { node: ">=6" } + + yargs-parser@20.2.9: + resolution: + { + integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==, + } + engines: { node: ">=10" } + + yargs-parser@21.1.1: + resolution: + { + integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==, + } + engines: { node: ">=12" } + + yargs-parser@22.0.0: + resolution: + { + integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==, + } + engines: { node: ^20.19.0 || ^22.12.0 || >=23 } + + yargs@15.4.1: + resolution: + { + integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==, + } + engines: { node: ">=8" } + + yargs@16.2.0: + resolution: + { + integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==, + } + engines: { node: ">=10" } + + yargs@17.7.2: + resolution: + { + integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==, + } + engines: { node: ">=12" } + + yargs@18.0.0: + resolution: + { + integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==, + } + engines: { node: ^20.19.0 || ^22.12.0 || >=23 } + + yocto-queue@0.1.0: + resolution: + { + integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, + } + engines: { node: ">=10" } + + yoctocolors@2.1.2: + resolution: + { + integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==, + } + engines: { node: ">=18" } + + zod@4.3.6: + resolution: + { + integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==, + } + +snapshots: + "@actions/core@3.0.0": + dependencies: + "@actions/exec": 3.0.0 + "@actions/http-client": 4.0.0 + + "@actions/exec@3.0.0": + dependencies: + "@actions/io": 3.0.2 + + "@actions/http-client@4.0.0": + dependencies: + tunnel: 0.0.6 + undici: 6.23.0 + + "@actions/io@3.0.2": {} + + "@alloc/quick-lru@5.2.0": {} + + "@amcharts/amcharts4-geodata@4.1.31": {} + + "@amcharts/amcharts4@4.10.40": + dependencies: + "@babel/runtime": 7.28.6 + core-js: 3.48.0 + d3-force: 3.0.0 + d3-geo: 3.1.1 + d3-geo-projection: 4.0.0 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + pdfmake: 0.2.23 + polylabel: 1.1.0 + raf: 3.4.1 + regression: 2.0.1 + rgbcolor: 1.0.1 + stackblur-canvas: 2.7.0 + tslib: 2.8.1 + + "@apideck/better-ajv-errors@0.3.6(ajv@8.18.0)": + dependencies: + ajv: 8.18.0 + json-schema: 0.4.0 + jsonpointer: 5.0.1 + leven: 3.1.0 + + "@babel/code-frame@7.29.0": + dependencies: + "@babel/helper-validator-identifier": 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + "@babel/compat-data@7.29.0": {} + + "@babel/core@7.29.0": + dependencies: + "@babel/code-frame": 7.29.0 + "@babel/generator": 7.29.1 + "@babel/helper-compilation-targets": 7.28.6 + "@babel/helper-module-transforms": 7.28.6(@babel/core@7.29.0) + "@babel/helpers": 7.28.6 + "@babel/parser": 7.29.0 + "@babel/template": 7.28.6 + "@babel/traverse": 7.29.0 + "@babel/types": 7.29.0 + "@jridgewell/remapping": 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + "@babel/generator@7.29.1": + dependencies: + "@babel/parser": 7.29.0 + "@babel/types": 7.29.0 + "@jridgewell/gen-mapping": 0.3.13 + "@jridgewell/trace-mapping": 0.3.31 + jsesc: 3.1.0 + + "@babel/helper-annotate-as-pure@7.27.3": + dependencies: + "@babel/types": 7.29.0 + + "@babel/helper-compilation-targets@7.28.6": + dependencies: + "@babel/compat-data": 7.29.0 + "@babel/helper-validator-option": 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + "@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-annotate-as-pure": 7.27.3 + "@babel/helper-member-expression-to-functions": 7.28.5 + "@babel/helper-optimise-call-expression": 7.27.1 + "@babel/helper-replace-supers": 7.28.6(@babel/core@7.29.0) + "@babel/helper-skip-transparent-expression-wrappers": 7.27.1 + "@babel/traverse": 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + "@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-annotate-as-pure": 7.27.3 + regexpu-core: 6.4.0 + semver: 6.3.1 + + "@babel/helper-define-polyfill-provider@0.6.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-compilation-targets": 7.28.6 + "@babel/helper-plugin-utils": 7.28.6 + debug: 4.4.3 + lodash.debounce: 4.0.8 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color + + "@babel/helper-globals@7.28.0": {} + + "@babel/helper-member-expression-to-functions@7.28.5": + dependencies: + "@babel/traverse": 7.29.0 + "@babel/types": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/helper-module-imports@7.28.6": + dependencies: + "@babel/traverse": 7.29.0 + "@babel/types": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-module-imports": 7.28.6 + "@babel/helper-validator-identifier": 7.28.5 + "@babel/traverse": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/helper-optimise-call-expression@7.27.1": + dependencies: + "@babel/types": 7.29.0 + + "@babel/helper-plugin-utils@7.28.6": {} + + "@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-annotate-as-pure": 7.27.3 + "@babel/helper-wrap-function": 7.28.6 + "@babel/traverse": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-member-expression-to-functions": 7.28.5 + "@babel/helper-optimise-call-expression": 7.27.1 + "@babel/traverse": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/helper-skip-transparent-expression-wrappers@7.27.1": + dependencies: + "@babel/traverse": 7.29.0 + "@babel/types": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/helper-string-parser@7.27.1": {} + + "@babel/helper-validator-identifier@7.28.5": {} + + "@babel/helper-validator-option@7.27.1": {} + + "@babel/helper-wrap-function@7.28.6": + dependencies: + "@babel/template": 7.28.6 + "@babel/traverse": 7.29.0 + "@babel/types": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/helpers@7.28.6": + dependencies: + "@babel/template": 7.28.6 + "@babel/types": 7.29.0 + + "@babel/parser@7.29.0": + dependencies: + "@babel/types": 7.29.0 + + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/traverse": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/helper-skip-transparent-expression-wrappers": 7.27.1 + "@babel/plugin-transform-optional-chaining": 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/traverse": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + + "@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-create-regexp-features-plugin": 7.28.5(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/helper-remap-async-to-generator": 7.27.1(@babel/core@7.29.0) + "@babel/traverse": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-module-imports": 7.28.6 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/helper-remap-async-to-generator": 7.27.1(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-create-class-features-plugin": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-create-class-features-plugin": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-annotate-as-pure": 7.27.3 + "@babel/helper-compilation-targets": 7.28.6 + "@babel/helper-globals": 7.28.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/helper-replace-supers": 7.28.6(@babel/core@7.29.0) + "@babel/traverse": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/template": 7.28.6 + + "@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/traverse": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-create-regexp-features-plugin": 7.28.5(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-create-regexp-features-plugin": 7.28.5(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/plugin-transform-destructuring": 7.28.5(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/helper-skip-transparent-expression-wrappers": 7.27.1 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-compilation-targets": 7.28.6 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/traverse": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-module-transforms": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-module-transforms": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-module-transforms": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + "@babel/helper-validator-identifier": 7.28.5 + "@babel/traverse": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-module-transforms": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-create-regexp-features-plugin": 7.28.5(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-compilation-targets": 7.28.6 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/plugin-transform-destructuring": 7.28.5(@babel/core@7.29.0) + "@babel/plugin-transform-parameters": 7.27.7(@babel/core@7.29.0) + "@babel/traverse": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/helper-replace-supers": 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/helper-skip-transparent-expression-wrappers": 7.27.1 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-create-class-features-plugin": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-annotate-as-pure": 7.27.3 + "@babel/helper-create-class-features-plugin": 7.28.6(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-create-regexp-features-plugin": 7.28.5(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/helper-skip-transparent-expression-wrappers": 7.27.1 + transitivePeerDependencies: + - supports-color + + "@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-create-regexp-features-plugin": 7.28.5(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-create-regexp-features-plugin": 7.28.5(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-create-regexp-features-plugin": 7.28.5(@babel/core@7.29.0) + "@babel/helper-plugin-utils": 7.28.6 + + "@babel/preset-env@7.29.0(@babel/core@7.29.0)": + dependencies: + "@babel/compat-data": 7.29.0 + "@babel/core": 7.29.0 + "@babel/helper-compilation-targets": 7.28.6 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/helper-validator-option": 7.27.1 + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": 7.28.5(@babel/core@7.29.0) + "@babel/plugin-bugfix-safari-class-field-initializer-scope": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-proposal-private-property-in-object": 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) + "@babel/plugin-syntax-import-assertions": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-syntax-import-attributes": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-syntax-unicode-sets-regex": 7.18.6(@babel/core@7.29.0) + "@babel/plugin-transform-arrow-functions": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-async-generator-functions": 7.29.0(@babel/core@7.29.0) + "@babel/plugin-transform-async-to-generator": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-block-scoped-functions": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-block-scoping": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-class-properties": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-class-static-block": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-classes": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-computed-properties": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-destructuring": 7.28.5(@babel/core@7.29.0) + "@babel/plugin-transform-dotall-regex": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-duplicate-keys": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": 7.29.0(@babel/core@7.29.0) + "@babel/plugin-transform-dynamic-import": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-explicit-resource-management": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-exponentiation-operator": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-export-namespace-from": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-for-of": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-function-name": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-json-strings": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-literals": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-logical-assignment-operators": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-member-expression-literals": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-modules-amd": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-modules-commonjs": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-modules-systemjs": 7.29.0(@babel/core@7.29.0) + "@babel/plugin-transform-modules-umd": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-named-capturing-groups-regex": 7.29.0(@babel/core@7.29.0) + "@babel/plugin-transform-new-target": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-nullish-coalescing-operator": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-numeric-separator": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-object-rest-spread": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-object-super": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-optional-catch-binding": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-optional-chaining": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-parameters": 7.27.7(@babel/core@7.29.0) + "@babel/plugin-transform-private-methods": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-private-property-in-object": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-property-literals": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-regenerator": 7.29.0(@babel/core@7.29.0) + "@babel/plugin-transform-regexp-modifiers": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-reserved-words": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-shorthand-properties": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-spread": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-sticky-regex": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-template-literals": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-typeof-symbol": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-unicode-escapes": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-unicode-property-regex": 7.28.6(@babel/core@7.29.0) + "@babel/plugin-transform-unicode-regex": 7.27.1(@babel/core@7.29.0) + "@babel/plugin-transform-unicode-sets-regex": 7.28.6(@babel/core@7.29.0) + "@babel/preset-modules": 0.1.6-no-external-plugins(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.15(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.14.0(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.6(@babel/core@7.29.0) + core-js-compat: 3.48.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + "@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-plugin-utils": 7.28.6 + "@babel/types": 7.29.0 + esutils: 2.0.3 + + "@babel/runtime@7.28.6": {} + + "@babel/template@7.28.6": + dependencies: + "@babel/code-frame": 7.29.0 + "@babel/parser": 7.29.0 + "@babel/types": 7.29.0 + + "@babel/traverse@7.29.0": + dependencies: + "@babel/code-frame": 7.29.0 + "@babel/generator": 7.29.1 + "@babel/helper-globals": 7.28.0 + "@babel/parser": 7.29.0 + "@babel/template": 7.28.6 + "@babel/types": 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + "@babel/types@7.29.0": + dependencies: + "@babel/helper-string-parser": 7.27.1 + "@babel/helper-validator-identifier": 7.28.5 + + "@cacheable/memory@2.0.7": + dependencies: + "@cacheable/utils": 2.3.4 + "@keyv/bigmap": 1.3.1(keyv@5.6.0) + hookified: 1.15.1 + keyv: 5.6.0 + + "@cacheable/utils@2.3.4": + dependencies: + hashery: 1.5.0 + keyv: 5.6.0 + + "@codemirror/autocomplete@6.20.0": + dependencies: + "@codemirror/language": 6.12.1 + "@codemirror/state": 6.5.4 + "@codemirror/view": 6.39.14 + "@lezer/common": 1.5.1 + + "@codemirror/commands@6.10.2": + dependencies: + "@codemirror/language": 6.12.1 + "@codemirror/state": 6.5.4 + "@codemirror/view": 6.39.14 + "@lezer/common": 1.5.1 + + "@codemirror/lang-css@6.3.1": + dependencies: + "@codemirror/autocomplete": 6.20.0 + "@codemirror/language": 6.12.1 + "@codemirror/state": 6.5.4 + "@lezer/common": 1.5.1 + "@lezer/css": 1.3.1 + + "@codemirror/lang-html@6.4.11": + dependencies: + "@codemirror/autocomplete": 6.20.0 + "@codemirror/lang-css": 6.3.1 + "@codemirror/lang-javascript": 6.2.4 + "@codemirror/language": 6.12.1 + "@codemirror/state": 6.5.4 + "@codemirror/view": 6.39.14 + "@lezer/common": 1.5.1 + "@lezer/css": 1.3.1 + "@lezer/html": 1.3.13 + + "@codemirror/lang-javascript@6.2.4": + dependencies: + "@codemirror/autocomplete": 6.20.0 + "@codemirror/language": 6.12.1 + "@codemirror/lint": 6.9.4 + "@codemirror/state": 6.5.4 + "@codemirror/view": 6.39.14 + "@lezer/common": 1.5.1 + "@lezer/javascript": 1.5.4 + + "@codemirror/lang-xml@6.1.0": + dependencies: + "@codemirror/autocomplete": 6.20.0 + "@codemirror/language": 6.12.1 + "@codemirror/state": 6.5.4 + "@codemirror/view": 6.39.14 + "@lezer/common": 1.5.1 + "@lezer/xml": 1.0.6 + + "@codemirror/language@6.12.1": + dependencies: + "@codemirror/state": 6.5.4 + "@codemirror/view": 6.39.14 + "@lezer/common": 1.5.1 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.8 + style-mod: 4.1.3 + + "@codemirror/lint@6.9.4": + dependencies: + "@codemirror/state": 6.5.4 + "@codemirror/view": 6.39.14 + crelt: 1.0.6 + + "@codemirror/search@6.6.0": + dependencies: + "@codemirror/state": 6.5.4 + "@codemirror/view": 6.39.14 + crelt: 1.0.6 + + "@codemirror/state@6.5.4": + dependencies: + "@marijn/find-cluster-break": 1.0.2 + + "@codemirror/view@6.39.14": + dependencies: + "@codemirror/state": 6.5.4 + crelt: 1.0.6 + style-mod: 4.1.3 + w3c-keyname: 2.2.8 + + "@colors/colors@1.5.0": + optional: true + + "@commitlint/cli@20.4.2(@types/node@24.7.0)(typescript@5.9.3)": + dependencies: + "@commitlint/format": 20.4.0 + "@commitlint/lint": 20.4.2 + "@commitlint/load": 20.4.0(@types/node@24.7.0)(typescript@5.9.3) + "@commitlint/read": 20.4.0 + "@commitlint/types": 20.4.0 + tinyexec: 1.0.2 + yargs: 17.7.2 + transitivePeerDependencies: + - "@types/node" + - typescript + + "@commitlint/config-conventional@20.4.2": + dependencies: + "@commitlint/types": 20.4.0 + conventional-changelog-conventionalcommits: 9.1.0 + + "@commitlint/config-validator@20.4.0": + dependencies: + "@commitlint/types": 20.4.0 + ajv: 8.18.0 + + "@commitlint/ensure@20.4.1": + dependencies: + "@commitlint/types": 20.4.0 + lodash.camelcase: 4.3.0 + lodash.kebabcase: 4.1.1 + lodash.snakecase: 4.1.1 + lodash.startcase: 4.4.0 + lodash.upperfirst: 4.3.1 + + "@commitlint/execute-rule@20.0.0": {} + + "@commitlint/format@20.4.0": + dependencies: + "@commitlint/types": 20.4.0 + picocolors: 1.1.1 + + "@commitlint/is-ignored@20.4.1": + dependencies: + "@commitlint/types": 20.4.0 + semver: 7.7.4 + + "@commitlint/lint@20.4.2": + dependencies: + "@commitlint/is-ignored": 20.4.1 + "@commitlint/parse": 20.4.1 + "@commitlint/rules": 20.4.2 + "@commitlint/types": 20.4.0 + + "@commitlint/load@20.4.0(@types/node@24.7.0)(typescript@5.9.3)": + dependencies: + "@commitlint/config-validator": 20.4.0 + "@commitlint/execute-rule": 20.0.0 + "@commitlint/resolve-extends": 20.4.0 + "@commitlint/types": 20.4.0 + cosmiconfig: 9.0.0(typescript@5.9.3) + cosmiconfig-typescript-loader: 6.2.0(@types/node@24.7.0)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3) + is-plain-obj: 4.1.0 + lodash.mergewith: 4.6.2 + picocolors: 1.1.1 + transitivePeerDependencies: + - "@types/node" + - typescript + + "@commitlint/message@20.4.0": {} + + "@commitlint/parse@20.4.1": + dependencies: + "@commitlint/types": 20.4.0 + conventional-changelog-angular: 8.1.0 + conventional-commits-parser: 6.2.1 + + "@commitlint/read@20.4.0": + dependencies: + "@commitlint/top-level": 20.4.0 + "@commitlint/types": 20.4.0 + git-raw-commits: 4.0.0 + minimist: 1.2.8 + tinyexec: 1.0.2 + + "@commitlint/resolve-extends@20.4.0": + dependencies: + "@commitlint/config-validator": 20.4.0 + "@commitlint/types": 20.4.0 + global-directory: 4.0.1 + import-meta-resolve: 4.2.0 + lodash.mergewith: 4.6.2 + resolve-from: 5.0.0 + + "@commitlint/rules@20.4.2": + dependencies: + "@commitlint/ensure": 20.4.1 + "@commitlint/message": 20.4.0 + "@commitlint/to-lines": 20.0.0 + "@commitlint/types": 20.4.0 + + "@commitlint/to-lines@20.0.0": {} + + "@commitlint/top-level@20.4.0": + dependencies: + escalade: 3.2.0 + + "@commitlint/types@20.4.0": + dependencies: + conventional-commits-parser: 6.2.1 + picocolors: 1.1.1 + + "@csstools/cascade-layer-name-parser@3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)": + dependencies: + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + + "@csstools/color-helpers@6.0.1": {} + + "@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)": + dependencies: + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + + "@csstools/css-color-parser@4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)": + dependencies: + "@csstools/color-helpers": 6.0.1 + "@csstools/css-calc": 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + + "@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)": + dependencies: + "@csstools/css-tokenizer": 4.0.0 + + "@csstools/css-syntax-patches-for-csstree@1.0.27": {} + + "@csstools/css-tokenizer@4.0.0": {} + + "@csstools/media-query-list-parser@5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)": + dependencies: + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + + "@csstools/postcss-alpha-function@2.0.2(postcss@8.5.6)": + dependencies: + "@csstools/css-color-parser": 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/postcss-progressive-custom-properties": 5.0.0(postcss@8.5.6) + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + + "@csstools/postcss-cascade-layers@6.0.0(postcss@8.5.6)": + dependencies: + "@csstools/selector-specificity": 6.0.0(postcss-selector-parser@7.1.1) + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + + "@csstools/postcss-color-function-display-p3-linear@2.0.1(postcss@8.5.6)": + dependencies: + "@csstools/css-color-parser": 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/postcss-progressive-custom-properties": 5.0.0(postcss@8.5.6) + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + + "@csstools/postcss-color-function@5.0.1(postcss@8.5.6)": + dependencies: + "@csstools/css-color-parser": 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/postcss-progressive-custom-properties": 5.0.0(postcss@8.5.6) + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + + "@csstools/postcss-color-mix-function@4.0.1(postcss@8.5.6)": + dependencies: + "@csstools/css-color-parser": 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/postcss-progressive-custom-properties": 5.0.0(postcss@8.5.6) + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + + "@csstools/postcss-color-mix-variadic-function-arguments@2.0.1(postcss@8.5.6)": + dependencies: + "@csstools/css-color-parser": 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/postcss-progressive-custom-properties": 5.0.0(postcss@8.5.6) + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + + "@csstools/postcss-content-alt-text@3.0.0(postcss@8.5.6)": + dependencies: + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/postcss-progressive-custom-properties": 5.0.0(postcss@8.5.6) + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + + "@csstools/postcss-contrast-color-function@3.0.1(postcss@8.5.6)": + dependencies: + "@csstools/css-color-parser": 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/postcss-progressive-custom-properties": 5.0.0(postcss@8.5.6) + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + + "@csstools/postcss-exponential-functions@3.0.0(postcss@8.5.6)": + dependencies: + "@csstools/css-calc": 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + postcss: 8.5.6 + + "@csstools/postcss-font-format-keywords@5.0.0(postcss@8.5.6)": + dependencies: + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + "@csstools/postcss-gamut-mapping@3.0.1(postcss@8.5.6)": + dependencies: + "@csstools/css-color-parser": 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + postcss: 8.5.6 + + "@csstools/postcss-gradients-interpolation-method@6.0.1(postcss@8.5.6)": + dependencies: + "@csstools/css-color-parser": 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/postcss-progressive-custom-properties": 5.0.0(postcss@8.5.6) + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + + "@csstools/postcss-hwb-function@5.0.1(postcss@8.5.6)": + dependencies: + "@csstools/css-color-parser": 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/postcss-progressive-custom-properties": 5.0.0(postcss@8.5.6) + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + + "@csstools/postcss-ic-unit@5.0.0(postcss@8.5.6)": + dependencies: + "@csstools/postcss-progressive-custom-properties": 5.0.0(postcss@8.5.6) + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + "@csstools/postcss-initial@3.0.0(postcss@8.5.6)": + dependencies: + postcss: 8.5.6 + + "@csstools/postcss-is-pseudo-class@6.0.0(postcss@8.5.6)": + dependencies: + "@csstools/selector-specificity": 6.0.0(postcss-selector-parser@7.1.1) + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + + "@csstools/postcss-light-dark-function@3.0.0(postcss@8.5.6)": + dependencies: + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/postcss-progressive-custom-properties": 5.0.0(postcss@8.5.6) + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + + "@csstools/postcss-logical-float-and-clear@4.0.0(postcss@8.5.6)": + dependencies: + postcss: 8.5.6 + + "@csstools/postcss-logical-overflow@3.0.0(postcss@8.5.6)": + dependencies: + postcss: 8.5.6 + + "@csstools/postcss-logical-overscroll-behavior@3.0.0(postcss@8.5.6)": + dependencies: + postcss: 8.5.6 + + "@csstools/postcss-logical-resize@4.0.0(postcss@8.5.6)": + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + "@csstools/postcss-logical-viewport-units@4.0.0(postcss@8.5.6)": + dependencies: + "@csstools/css-tokenizer": 4.0.0 + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + + "@csstools/postcss-media-minmax@3.0.0(postcss@8.5.6)": + dependencies: + "@csstools/css-calc": 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/media-query-list-parser": 5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + postcss: 8.5.6 + + "@csstools/postcss-media-queries-aspect-ratio-number-values@4.0.0(postcss@8.5.6)": + dependencies: + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/media-query-list-parser": 5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + postcss: 8.5.6 + + "@csstools/postcss-mixins@1.0.0(postcss@8.5.6)": + dependencies: + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + postcss: 8.5.6 + + "@csstools/postcss-nested-calc@5.0.0(postcss@8.5.6)": + dependencies: + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + "@csstools/postcss-normalize-display-values@5.0.1(postcss@8.5.6)": + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + "@csstools/postcss-oklab-function@5.0.1(postcss@8.5.6)": + dependencies: + "@csstools/css-color-parser": 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/postcss-progressive-custom-properties": 5.0.0(postcss@8.5.6) + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + + "@csstools/postcss-position-area-property@2.0.0(postcss@8.5.6)": + dependencies: + postcss: 8.5.6 + + "@csstools/postcss-progressive-custom-properties@5.0.0(postcss@8.5.6)": + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + "@csstools/postcss-property-rule-prelude-list@2.0.0(postcss@8.5.6)": + dependencies: + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + postcss: 8.5.6 + + "@csstools/postcss-random-function@3.0.0(postcss@8.5.6)": + dependencies: + "@csstools/css-calc": 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + postcss: 8.5.6 + + "@csstools/postcss-relative-color-syntax@4.0.1(postcss@8.5.6)": + dependencies: + "@csstools/css-color-parser": 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/postcss-progressive-custom-properties": 5.0.0(postcss@8.5.6) + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + + "@csstools/postcss-scope-pseudo-class@5.0.0(postcss@8.5.6)": + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + + "@csstools/postcss-sign-functions@2.0.0(postcss@8.5.6)": + dependencies: + "@csstools/css-calc": 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + postcss: 8.5.6 + + "@csstools/postcss-stepped-value-functions@5.0.0(postcss@8.5.6)": + dependencies: + "@csstools/css-calc": 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + postcss: 8.5.6 + + "@csstools/postcss-syntax-descriptor-syntax-production@2.0.0(postcss@8.5.6)": + dependencies: + "@csstools/css-tokenizer": 4.0.0 + postcss: 8.5.6 + + "@csstools/postcss-system-ui-font-family@2.0.0(postcss@8.5.6)": + dependencies: + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + postcss: 8.5.6 + + "@csstools/postcss-text-decoration-shorthand@5.0.2(postcss@8.5.6)": + dependencies: + "@csstools/color-helpers": 6.0.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + "@csstools/postcss-trigonometric-functions@5.0.0(postcss@8.5.6)": + dependencies: + "@csstools/css-calc": 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + postcss: 8.5.6 + + "@csstools/postcss-unset-value@5.0.0(postcss@8.5.6)": + dependencies: + postcss: 8.5.6 + + "@csstools/selector-resolve-nested@4.0.0(postcss-selector-parser@7.1.1)": + dependencies: + postcss-selector-parser: 7.1.1 + + "@csstools/selector-specificity@6.0.0(postcss-selector-parser@7.1.1)": + dependencies: + postcss-selector-parser: 7.1.1 + + "@csstools/utilities@3.0.0(postcss@8.5.6)": + dependencies: + postcss: 8.5.6 + + "@emnapi/runtime@1.8.1": + dependencies: + tslib: 2.8.1 + optional: true + + "@epic-web/invariant@1.0.0": {} + + "@esbuild/aix-ppc64@0.27.3": + optional: true + + "@esbuild/android-arm64@0.27.3": + optional: true + + "@esbuild/android-arm@0.27.3": + optional: true + + "@esbuild/android-x64@0.27.3": + optional: true + + "@esbuild/darwin-arm64@0.27.3": + optional: true + + "@esbuild/darwin-x64@0.27.3": + optional: true + + "@esbuild/freebsd-arm64@0.27.3": + optional: true + + "@esbuild/freebsd-x64@0.27.3": + optional: true + + "@esbuild/linux-arm64@0.27.3": + optional: true + + "@esbuild/linux-arm@0.27.3": + optional: true + + "@esbuild/linux-ia32@0.27.3": + optional: true + + "@esbuild/linux-loong64@0.27.3": + optional: true + + "@esbuild/linux-mips64el@0.27.3": + optional: true + + "@esbuild/linux-ppc64@0.27.3": + optional: true + + "@esbuild/linux-riscv64@0.27.3": + optional: true + + "@esbuild/linux-s390x@0.27.3": + optional: true + + "@esbuild/linux-x64@0.27.3": + optional: true + + "@esbuild/netbsd-arm64@0.27.3": + optional: true + + "@esbuild/netbsd-x64@0.27.3": + optional: true + + "@esbuild/openbsd-arm64@0.27.3": + optional: true + + "@esbuild/openbsd-x64@0.27.3": + optional: true + + "@esbuild/openharmony-arm64@0.27.3": + optional: true + + "@esbuild/sunos-x64@0.27.3": + optional: true + + "@esbuild/win32-arm64@0.27.3": + optional: true + + "@esbuild/win32-ia32@0.27.3": + optional: true + + "@esbuild/win32-x64@0.27.3": + optional: true + + "@eslint-community/eslint-utils@4.9.1(eslint@10.0.0(jiti@1.21.7))": + dependencies: + eslint: 10.0.0(jiti@1.21.7) + eslint-visitor-keys: 3.4.3 + + "@eslint-community/regexpp@4.12.2": {} + + "@eslint/config-array@0.23.1": + dependencies: + "@eslint/object-schema": 3.0.1 + debug: 4.4.3 + minimatch: 10.2.1 + transitivePeerDependencies: + - supports-color + + "@eslint/config-helpers@0.5.2": + dependencies: + "@eslint/core": 1.1.0 + + "@eslint/core@1.1.0": + dependencies: + "@types/json-schema": 7.0.15 + + "@eslint/eslintrc@3.3.3": + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + "@eslint/js@10.0.1(eslint@10.0.0(jiti@1.21.7))": + optionalDependencies: + eslint: 10.0.0(jiti@1.21.7) + + "@eslint/object-schema@3.0.1": {} + + "@eslint/plugin-kit@0.6.0": + dependencies: + "@eslint/core": 1.1.0 + levn: 0.4.1 + + "@floating-ui/core@1.7.4": + dependencies: + "@floating-ui/utils": 0.2.10 + + "@floating-ui/dom@1.7.5": + dependencies: + "@floating-ui/core": 1.7.4 + "@floating-ui/utils": 0.2.10 + + "@floating-ui/utils@0.2.10": {} + + "@foliojs-fork/fontkit@1.9.2": + dependencies: + "@foliojs-fork/restructure": 2.0.2 + brotli: 1.3.3 + clone: 1.0.4 + deep-equal: 1.1.2 + dfa: 1.2.0 + tiny-inflate: 1.0.3 + unicode-properties: 1.4.1 + unicode-trie: 2.0.0 + + "@foliojs-fork/linebreak@1.1.2": + dependencies: + base64-js: 1.3.1 + unicode-trie: 2.0.0 + + "@foliojs-fork/pdfkit@0.15.3": + dependencies: + "@foliojs-fork/fontkit": 1.9.2 + "@foliojs-fork/linebreak": 1.1.2 + crypto-js: 4.2.0 + jpeg-exif: 1.1.4 + png-js: 1.0.0 + + "@foliojs-fork/restructure@2.0.2": {} + + "@github/clipboard-copy-element@1.3.0": {} + + "@github/hotkey@3.1.1": {} + + "@github/markdown-toolbar-element@2.2.3": {} + + "@github/relative-time-element@5.0.0": {} + + "@humanfs/core@0.19.1": {} + + "@humanfs/node@0.16.7": + dependencies: + "@humanfs/core": 0.19.1 + "@humanwhocodes/retry": 0.4.3 + + "@humanwhocodes/module-importer@1.0.1": {} + + "@humanwhocodes/retry@0.4.3": {} + + "@img/colour@1.0.0": {} + + "@img/sharp-darwin-arm64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-darwin-arm64": 1.2.4 + optional: true + + "@img/sharp-darwin-x64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-darwin-x64": 1.2.4 + optional: true + + "@img/sharp-libvips-darwin-arm64@1.2.4": + optional: true + + "@img/sharp-libvips-darwin-x64@1.2.4": + optional: true + + "@img/sharp-libvips-linux-arm64@1.2.4": + optional: true + + "@img/sharp-libvips-linux-arm@1.2.4": + optional: true + + "@img/sharp-libvips-linux-ppc64@1.2.4": + optional: true + + "@img/sharp-libvips-linux-riscv64@1.2.4": + optional: true + + "@img/sharp-libvips-linux-s390x@1.2.4": + optional: true + + "@img/sharp-libvips-linux-x64@1.2.4": + optional: true + + "@img/sharp-libvips-linuxmusl-arm64@1.2.4": + optional: true + + "@img/sharp-libvips-linuxmusl-x64@1.2.4": + optional: true + + "@img/sharp-linux-arm64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-arm64": 1.2.4 + optional: true + + "@img/sharp-linux-arm@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-arm": 1.2.4 + optional: true + + "@img/sharp-linux-ppc64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-ppc64": 1.2.4 + optional: true + + "@img/sharp-linux-riscv64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-riscv64": 1.2.4 + optional: true + + "@img/sharp-linux-s390x@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-s390x": 1.2.4 + optional: true + + "@img/sharp-linux-x64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linux-x64": 1.2.4 + optional: true + + "@img/sharp-linuxmusl-arm64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64": 1.2.4 + optional: true + + "@img/sharp-linuxmusl-x64@0.34.5": + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64": 1.2.4 + optional: true + + "@img/sharp-wasm32@0.34.5": + dependencies: + "@emnapi/runtime": 1.8.1 + optional: true + + "@img/sharp-win32-arm64@0.34.5": + optional: true + + "@img/sharp-win32-ia32@0.34.5": + optional: true + + "@img/sharp-win32-x64@0.34.5": + optional: true + + "@isaacs/cliui@9.0.0": {} + + "@jridgewell/gen-mapping@0.3.13": + dependencies: + "@jridgewell/sourcemap-codec": 1.5.5 + "@jridgewell/trace-mapping": 0.3.31 + + "@jridgewell/remapping@2.3.5": + dependencies: + "@jridgewell/gen-mapping": 0.3.13 + "@jridgewell/trace-mapping": 0.3.31 + + "@jridgewell/resolve-uri@3.1.2": {} + + "@jridgewell/source-map@0.3.11": + dependencies: + "@jridgewell/gen-mapping": 0.3.13 + "@jridgewell/trace-mapping": 0.3.31 + + "@jridgewell/sourcemap-codec@1.5.5": {} + + "@jridgewell/trace-mapping@0.3.31": + dependencies: + "@jridgewell/resolve-uri": 3.1.2 + "@jridgewell/sourcemap-codec": 1.5.5 + + "@keyv/bigmap@1.3.1(keyv@5.6.0)": + dependencies: + hashery: 1.5.0 + hookified: 1.15.1 + keyv: 5.6.0 + + "@keyv/serialize@1.1.1": {} + + "@lezer/common@1.5.1": {} + + "@lezer/css@1.3.1": + dependencies: + "@lezer/common": 1.5.1 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.8 + + "@lezer/highlight@1.2.3": + dependencies: + "@lezer/common": 1.5.1 + + "@lezer/html@1.3.13": + dependencies: + "@lezer/common": 1.5.1 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.8 + + "@lezer/javascript@1.5.4": + dependencies: + "@lezer/common": 1.5.1 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.8 + + "@lezer/lr@1.4.8": + dependencies: + "@lezer/common": 1.5.1 + + "@lezer/xml@1.0.6": + dependencies: + "@lezer/common": 1.5.1 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.8 + + "@lit-labs/ssr-dom-shim@1.5.1": {} + + "@lit/context@1.1.6": + dependencies: + "@lit/reactive-element": 2.1.2 + + "@lit/reactive-element@2.1.2": + dependencies: + "@lit-labs/ssr-dom-shim": 1.5.1 + + "@marijn/find-cluster-break@1.0.2": {} + + "@nodelib/fs.scandir@2.1.5": + dependencies: + "@nodelib/fs.stat": 2.0.5 + run-parallel: 1.2.0 + + "@nodelib/fs.stat@2.0.5": {} + + "@nodelib/fs.walk@1.2.8": + dependencies: + "@nodelib/fs.scandir": 2.1.5 + fastq: 1.20.1 + + "@octokit/auth-token@6.0.0": {} + + "@octokit/core@7.0.6": + dependencies: + "@octokit/auth-token": 6.0.0 + "@octokit/graphql": 9.0.3 + "@octokit/request": 10.0.7 + "@octokit/request-error": 7.1.0 + "@octokit/types": 16.0.0 + before-after-hook: 4.0.0 + universal-user-agent: 7.0.3 + + "@octokit/endpoint@11.0.3": + dependencies: + "@octokit/types": 16.0.0 + universal-user-agent: 7.0.3 + + "@octokit/graphql@9.0.3": + dependencies: + "@octokit/request": 10.0.7 + "@octokit/types": 16.0.0 + universal-user-agent: 7.0.3 + + "@octokit/openapi-types@27.0.0": {} + + "@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.6)": + dependencies: + "@octokit/core": 7.0.6 + "@octokit/types": 16.0.0 + + "@octokit/plugin-retry@8.1.0(@octokit/core@7.0.6)": + dependencies: + "@octokit/core": 7.0.6 + "@octokit/request-error": 7.1.0 + "@octokit/types": 16.0.0 + bottleneck: 2.19.5 + + "@octokit/plugin-throttling@11.0.3(@octokit/core@7.0.6)": + dependencies: + "@octokit/core": 7.0.6 + "@octokit/types": 16.0.0 + bottleneck: 2.19.5 + + "@octokit/request-error@7.1.0": + dependencies: + "@octokit/types": 16.0.0 + + "@octokit/request@10.0.7": + dependencies: + "@octokit/endpoint": 11.0.3 + "@octokit/request-error": 7.1.0 + "@octokit/types": 16.0.0 + fast-content-type-parse: 3.0.0 + universal-user-agent: 7.0.3 + + "@octokit/types@16.0.0": + dependencies: + "@octokit/openapi-types": 27.0.0 + + "@patternfly/elements@4.3.1": + dependencies: + "@lit/context": 1.1.6 + "@patternfly/icons": 1.0.3 + "@patternfly/pfe-core": 5.0.6 + lit: 3.3.2 + tslib: 2.8.1 + + "@patternfly/icons@1.0.3": {} + + "@patternfly/pfe-core@5.0.6": + dependencies: + "@lit/context": 1.1.6 + lit: 3.3.2 + + "@pkgr/core@0.2.9": {} + + "@pnpm/config.env-replace@1.1.0": {} + + "@pnpm/network.ca-file@1.0.2": + dependencies: + graceful-fs: 4.2.10 + + "@pnpm/npm-conf@3.0.2": + dependencies: + "@pnpm/config.env-replace": 1.1.0 + "@pnpm/network.ca-file": 1.0.2 + config-chain: 1.1.13 + + "@polka/url@1.0.0-next.29": {} + + "@rollup/plugin-babel@5.3.1(@babel/core@7.29.0)(rollup@2.79.2)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-module-imports": 7.28.6 + "@rollup/pluginutils": 3.1.0(rollup@2.79.2) + rollup: 2.79.2 + transitivePeerDependencies: + - supports-color + + "@rollup/plugin-node-resolve@15.3.1(rollup@2.79.2)": + dependencies: + "@rollup/pluginutils": 5.3.0(rollup@2.79.2) + "@types/resolve": 1.20.2 + deepmerge: 4.3.1 + is-module: 1.0.0 + resolve: 1.22.11 + optionalDependencies: + rollup: 2.79.2 + + "@rollup/plugin-replace@2.4.2(rollup@2.79.2)": + dependencies: + "@rollup/pluginutils": 3.1.0(rollup@2.79.2) + magic-string: 0.25.9 + rollup: 2.79.2 + + "@rollup/plugin-terser@0.4.4(rollup@2.79.2)": + dependencies: + serialize-javascript: 6.0.2 + smob: 1.6.1 + terser: 5.46.0 + optionalDependencies: + rollup: 2.79.2 + + "@rollup/pluginutils@3.1.0(rollup@2.79.2)": + dependencies: + "@types/estree": 0.0.39 + estree-walker: 1.0.1 + picomatch: 2.3.1 + rollup: 2.79.2 + + "@rollup/pluginutils@5.3.0(rollup@2.79.2)": + dependencies: + "@types/estree": 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 2.79.2 + + "@rollup/rollup-android-arm-eabi@4.57.1": + optional: true + + "@rollup/rollup-android-arm64@4.57.1": + optional: true + + "@rollup/rollup-darwin-arm64@4.57.1": + optional: true + + "@rollup/rollup-darwin-x64@4.57.1": + optional: true + + "@rollup/rollup-freebsd-arm64@4.57.1": + optional: true + + "@rollup/rollup-freebsd-x64@4.57.1": + optional: true + + "@rollup/rollup-linux-arm-gnueabihf@4.57.1": + optional: true + + "@rollup/rollup-linux-arm-musleabihf@4.57.1": + optional: true + + "@rollup/rollup-linux-arm64-gnu@4.57.1": + optional: true + + "@rollup/rollup-linux-arm64-musl@4.57.1": + optional: true + + "@rollup/rollup-linux-loong64-gnu@4.57.1": + optional: true + + "@rollup/rollup-linux-loong64-musl@4.57.1": + optional: true + + "@rollup/rollup-linux-ppc64-gnu@4.57.1": + optional: true + + "@rollup/rollup-linux-ppc64-musl@4.57.1": + optional: true + + "@rollup/rollup-linux-riscv64-gnu@4.57.1": + optional: true + + "@rollup/rollup-linux-riscv64-musl@4.57.1": + optional: true + + "@rollup/rollup-linux-s390x-gnu@4.57.1": + optional: true + + "@rollup/rollup-linux-x64-gnu@4.57.1": + optional: true + + "@rollup/rollup-linux-x64-musl@4.57.1": + optional: true + + "@rollup/rollup-openbsd-x64@4.57.1": + optional: true + + "@rollup/rollup-openharmony-arm64@4.57.1": + optional: true + + "@rollup/rollup-win32-arm64-msvc@4.57.1": + optional: true + + "@rollup/rollup-win32-ia32-msvc@4.57.1": + optional: true + + "@rollup/rollup-win32-x64-gnu@4.57.1": + optional: true + + "@rollup/rollup-win32-x64-msvc@4.57.1": + optional: true + + "@sec-ant/readable-stream@0.4.1": {} + + "@semantic-release/changelog@6.0.3(semantic-release@25.0.3(typescript@5.9.3))": + dependencies: + "@semantic-release/error": 3.0.0 + aggregate-error: 3.1.0 + fs-extra: 11.3.3 + lodash: 4.17.23 + semantic-release: 25.0.3(typescript@5.9.3) + + "@semantic-release/commit-analyzer@13.0.1(semantic-release@25.0.3(typescript@5.9.3))": + dependencies: + conventional-changelog-angular: 8.1.0 + conventional-changelog-writer: 8.2.0 + conventional-commits-filter: 5.0.0 + conventional-commits-parser: 6.2.1 + debug: 4.4.3 + import-from-esm: 2.0.0 + lodash-es: 4.17.23 + micromatch: 4.0.8 + semantic-release: 25.0.3(typescript@5.9.3) + transitivePeerDependencies: + - supports-color + + "@semantic-release/error@3.0.0": {} + + "@semantic-release/error@4.0.0": {} + + "@semantic-release/exec@7.1.0(semantic-release@25.0.3(typescript@5.9.3))": + dependencies: + "@semantic-release/error": 4.0.0 + aggregate-error: 3.1.0 + debug: 4.4.3 + execa: 9.6.1 + lodash-es: 4.17.23 + parse-json: 8.3.0 + semantic-release: 25.0.3(typescript@5.9.3) + transitivePeerDependencies: + - supports-color + + "@semantic-release/git@10.0.1(semantic-release@25.0.3(typescript@5.9.3))": + dependencies: + "@semantic-release/error": 3.0.0 + aggregate-error: 3.1.0 + debug: 4.4.3 + dir-glob: 3.0.1 + execa: 5.1.1 + lodash: 4.17.23 + micromatch: 4.0.8 + p-reduce: 2.1.0 + semantic-release: 25.0.3(typescript@5.9.3) + transitivePeerDependencies: + - supports-color + + "@semantic-release/github@12.0.6(semantic-release@25.0.3(typescript@5.9.3))": + dependencies: + "@octokit/core": 7.0.6 + "@octokit/plugin-paginate-rest": 14.0.0(@octokit/core@7.0.6) + "@octokit/plugin-retry": 8.1.0(@octokit/core@7.0.6) + "@octokit/plugin-throttling": 11.0.3(@octokit/core@7.0.6) + "@semantic-release/error": 4.0.0 + aggregate-error: 5.0.0 + debug: 4.4.3 + dir-glob: 3.0.1 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + issue-parser: 7.0.1 + lodash-es: 4.17.23 + mime: 4.1.0 + p-filter: 4.1.0 + semantic-release: 25.0.3(typescript@5.9.3) + tinyglobby: 0.2.15 + undici: 7.22.0 + url-join: 5.0.0 + transitivePeerDependencies: + - supports-color + + "@semantic-release/gitlab@13.3.0(semantic-release@25.0.3(typescript@5.9.3))": + dependencies: + "@semantic-release/error": 4.0.0 + aggregate-error: 5.0.0 + debug: 4.4.3 + dir-glob: 3.0.1 + escape-string-regexp: 5.0.0 + formdata-node: 6.0.3 + fs-extra: 11.3.3 + globby: 14.1.0 + got: 14.6.6 + hpagent: 1.2.0 + lodash-es: 4.17.23 + parse-url: 10.0.3 + semantic-release: 25.0.3(typescript@5.9.3) + url-join: 4.0.1 + transitivePeerDependencies: + - supports-color + + "@semantic-release/npm@13.1.4(semantic-release@25.0.3(typescript@5.9.3))": + dependencies: + "@actions/core": 3.0.0 + "@semantic-release/error": 4.0.0 + aggregate-error: 5.0.0 + env-ci: 11.2.0 + execa: 9.6.1 + fs-extra: 11.3.3 + lodash-es: 4.17.23 + nerf-dart: 1.0.0 + normalize-url: 8.1.1 + npm: 11.10.0 + rc: 1.2.8 + read-pkg: 10.1.0 + registry-auth-token: 5.1.1 + semantic-release: 25.0.3(typescript@5.9.3) + semver: 7.7.4 + tempy: 3.2.0 + + "@semantic-release/release-notes-generator@14.1.0(semantic-release@25.0.3(typescript@5.9.3))": + dependencies: + conventional-changelog-angular: 8.1.0 + conventional-changelog-writer: 8.2.0 + conventional-commits-filter: 5.0.0 + conventional-commits-parser: 6.2.1 + debug: 4.4.3 + get-stream: 7.0.1 + import-from-esm: 2.0.0 + into-stream: 7.0.0 + lodash-es: 4.17.23 + read-package-up: 11.0.0 + semantic-release: 25.0.3(typescript@5.9.3) + transitivePeerDependencies: + - supports-color + + "@sindresorhus/is@4.6.0": {} + + "@sindresorhus/is@7.2.0": {} + + "@sindresorhus/merge-streams@2.3.0": {} + + "@sindresorhus/merge-streams@4.0.0": {} + + "@stencil/core@2.5.2": {} + + "@surma/rollup-plugin-off-main-thread@2.2.3": + dependencies: + ejs: 3.1.10 + json5: 2.2.3 + magic-string: 0.25.9 + string.prototype.matchall: 4.0.12 + + "@tailwindcss/forms@0.5.11(tailwindcss@3.4.19(yaml@2.8.2))": + dependencies: + mini-svg-data-uri: 1.4.4 + tailwindcss: 3.4.19(yaml@2.8.2) + + "@tailwindcss/typography@0.5.19(tailwindcss@3.4.19(yaml@2.8.2))": + dependencies: + postcss-selector-parser: 6.0.10 + tailwindcss: 3.4.19(yaml@2.8.2) + + "@types/eslint@9.6.1": + dependencies: + "@types/estree": 1.0.8 + "@types/json-schema": 7.0.15 + optional: true + + "@types/esrecurse@4.3.1": {} + + "@types/estree@0.0.39": {} + + "@types/estree@1.0.8": {} + + "@types/fscreen@1.0.4": {} + + "@types/geojson@7946.0.16": {} + + "@types/http-cache-semantics@4.2.0": {} + + "@types/json-schema@7.0.15": {} + + "@types/leaflet@1.9.21": + dependencies: + "@types/geojson": 7946.0.16 + + "@types/node@24.7.0": + dependencies: + undici-types: 7.14.0 + + "@types/normalize-package-data@2.4.4": {} + + "@types/resolve@1.20.2": {} + + "@types/trusted-types@2.0.7": {} + + "@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3)": + dependencies: + "@eslint-community/regexpp": 4.12.2 + "@typescript-eslint/parser": 8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3) + "@typescript-eslint/scope-manager": 8.56.0 + "@typescript-eslint/type-utils": 8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3) + "@typescript-eslint/utils": 8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3) + "@typescript-eslint/visitor-keys": 8.56.0 + eslint: 10.0.0(jiti@1.21.7) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3)": + dependencies: + "@typescript-eslint/scope-manager": 8.56.0 + "@typescript-eslint/types": 8.56.0 + "@typescript-eslint/typescript-estree": 8.56.0(typescript@5.9.3) + "@typescript-eslint/visitor-keys": 8.56.0 + debug: 4.4.3 + eslint: 10.0.0(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/project-service@8.56.0(typescript@5.9.3)": + dependencies: + "@typescript-eslint/tsconfig-utils": 8.56.0(typescript@5.9.3) + "@typescript-eslint/types": 8.56.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/scope-manager@8.56.0": + dependencies: + "@typescript-eslint/types": 8.56.0 + "@typescript-eslint/visitor-keys": 8.56.0 + + "@typescript-eslint/tsconfig-utils@8.56.0(typescript@5.9.3)": + dependencies: + typescript: 5.9.3 + + "@typescript-eslint/type-utils@8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3)": + dependencies: + "@typescript-eslint/types": 8.56.0 + "@typescript-eslint/typescript-estree": 8.56.0(typescript@5.9.3) + "@typescript-eslint/utils": 8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3) + debug: 4.4.3 + eslint: 10.0.0(jiti@1.21.7) + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/types@8.56.0": {} + + "@typescript-eslint/typescript-estree@8.56.0(typescript@5.9.3)": + dependencies: + "@typescript-eslint/project-service": 8.56.0(typescript@5.9.3) + "@typescript-eslint/tsconfig-utils": 8.56.0(typescript@5.9.3) + "@typescript-eslint/types": 8.56.0 + "@typescript-eslint/visitor-keys": 8.56.0 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/utils@8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3)": + dependencies: + "@eslint-community/eslint-utils": 4.9.1(eslint@10.0.0(jiti@1.21.7)) + "@typescript-eslint/scope-manager": 8.56.0 + "@typescript-eslint/types": 8.56.0 + "@typescript-eslint/typescript-estree": 8.56.0(typescript@5.9.3) + eslint: 10.0.0(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/visitor-keys@8.56.0": + dependencies: + "@typescript-eslint/types": 8.56.0 + eslint-visitor-keys: 5.0.0 + + "@vime/core@5.4.1": + dependencies: + "@stencil/core": 2.5.2 + "@types/fscreen": 1.0.4 + fscreen: 1.2.0 + mitt: 3.0.1 + stencil-wormhole: 3.4.1 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + agent-base@7.1.4: {} + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + aggregate-error@5.0.0: + dependencies: + clean-stack: 5.3.0 + indent-string: 5.0.0 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + all-contributors-cli@6.26.1: + dependencies: + "@babel/runtime": 7.28.6 + async: 3.2.6 + chalk: 4.1.2 + didyoumean: 1.2.2 + inquirer: 7.3.3 + json-fixer: 1.6.15 + lodash: 4.17.23 + node-fetch: 2.7.0 + pify: 5.0.0 + yargs: 15.4.1 + optionalDependencies: + prettier: 2.8.8 + transitivePeerDependencies: + - encoding + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + ansis@4.2.0: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + argv-formatter@1.0.0: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-ify@1.0.0: {} + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + astral-regex@2.0.0: {} + + async-function@1.0.0: {} + + async@3.2.6: {} + + at-least-node@1.0.0: {} + + autoprefixer@10.4.24(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + caniuse-lite: 1.0.30001770 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + babel-plugin-polyfill-corejs2@0.4.15(@babel/core@7.29.0): + dependencies: + "@babel/compat-data": 7.29.0 + "@babel/core": 7.29.0 + "@babel/helper-define-polyfill-provider": 0.6.6(@babel/core@7.29.0) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.14.0(@babel/core@7.29.0): + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-define-polyfill-provider": 0.6.6(@babel/core@7.29.0) + core-js-compat: 3.48.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.6(@babel/core@7.29.0): + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-define-polyfill-provider": 0.6.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + balanced-match@1.0.2: {} + + balanced-match@3.0.1: {} + + balanced-match@4.0.3: {} + + base64-js@1.3.1: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.10.0: {} + + before-after-hook@4.0.0: {} + + binary-extensions@2.3.0: {} + + birpc@2.9.0: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + boolbase@1.0.0: {} + + bottleneck@2.19.5: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + brace-expansion@5.0.2: + dependencies: + balanced-match: 4.0.3 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + brotli@1.3.3: + dependencies: + base64-js: 1.5.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001770 + electron-to-chromium: 1.5.286 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + byte-counter@0.1.0: {} + + cacheable-lookup@7.0.0: {} + + cacheable-request@13.0.18: + dependencies: + "@types/http-cache-semantics": 4.2.0 + get-stream: 9.0.1 + http-cache-semantics: 4.2.0 + keyv: 5.6.0 + mimic-response: 4.0.0 + normalize-url: 8.1.1 + responselike: 4.0.2 + + cacheable@2.3.2: + dependencies: + "@cacheable/memory": 2.0.7 + "@cacheable/utils": 2.3.4 + hookified: 1.15.1 + keyv: 5.6.0 + qified: 0.6.0 + + cachedir@2.3.0: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + camelcase@5.3.1: {} + + caniuse-api@3.0.0: + dependencies: + browserslist: 4.28.1 + caniuse-lite: 1.0.30001770 + lodash.memoize: 4.1.2 + lodash.uniq: 4.5.0 + + caniuse-lite@1.0.30001770: {} + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + char-regex@1.0.2: {} + + chardet@0.7.0: {} + + choices.js@11.1.0: + dependencies: + fuse.js: 7.1.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + ci-info@4.4.0: {} + + clean-stack@2.2.0: {} + + clean-stack@5.3.0: + dependencies: + escape-string-regexp: 5.0.0 + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-highlight@2.1.11: + dependencies: + chalk: 4.1.2 + highlight.js: 10.7.3 + mz: 2.7.0 + parse5: 5.1.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + yargs: 16.2.0 + + cli-spinners@2.9.2: {} + + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + "@colors/colors": 1.5.0 + + cli-truncate@5.1.1: + dependencies: + slice-ansi: 7.1.2 + string-width: 8.2.0 + + cli-width@3.0.0: {} + + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.1.2 + wrap-ansi: 9.0.2 + + clone@1.0.4: {} + + codemirror@6.0.2: + dependencies: + "@codemirror/autocomplete": 6.20.0 + "@codemirror/commands": 6.10.2 + "@codemirror/language": 6.12.1 + "@codemirror/lint": 6.9.4 + "@codemirror/search": 6.6.0 + "@codemirror/state": 6.5.4 + "@codemirror/view": 6.39.14 + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + colord@2.9.3: {} + + colorette@2.0.20: {} + + commander@11.1.0: {} + + commander@14.0.3: {} + + commander@2.20.3: {} + + commander@4.1.1: {} + + commander@7.2.0: {} + + commitizen@4.3.1(@types/node@24.7.0)(typescript@5.9.3): + dependencies: + cachedir: 2.3.0 + cz-conventional-changelog: 3.3.0(@types/node@24.7.0)(typescript@5.9.3) + dedent: 0.7.0 + detect-indent: 6.1.0 + find-node-modules: 2.1.3 + find-root: 1.1.0 + fs-extra: 9.1.0 + glob: 7.2.3 + inquirer: 8.2.5 + is-utf8: 0.2.1 + lodash: 4.17.21 + minimist: 1.2.7 + strip-bom: 4.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - "@types/node" + - typescript + + common-tags@1.8.2: {} + + compare-func@2.0.0: + dependencies: + array-ify: 1.0.0 + dot-prop: 5.3.0 + + concat-map@0.0.1: {} + + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + + conventional-changelog-angular@8.1.0: + dependencies: + compare-func: 2.0.0 + + conventional-changelog-conventionalcommits@9.1.0: + dependencies: + compare-func: 2.0.0 + + conventional-changelog-writer@8.2.0: + dependencies: + conventional-commits-filter: 5.0.0 + handlebars: 4.7.8 + meow: 13.2.0 + semver: 7.7.4 + + conventional-commit-types@3.0.0: {} + + conventional-commits-filter@5.0.0: {} + + conventional-commits-parser@6.2.1: + dependencies: + meow: 13.2.0 + + convert-hrtime@5.0.0: {} + + convert-source-map@2.0.0: {} + + core-js-compat@3.48.0: + dependencies: + browserslist: 4.28.1 + + core-js@3.48.0: {} + + core-util-is@1.0.3: {} + + cosmiconfig-typescript-loader@6.2.0(@types/node@24.7.0)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): + dependencies: + "@types/node": 24.7.0 + cosmiconfig: 9.0.0(typescript@5.9.3) + jiti: 2.6.1 + typescript: 5.9.3 + + cosmiconfig@9.0.0(typescript@5.9.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.9.3 + + crelt@1.0.6: {} + + cross-env@10.1.0: + dependencies: + "@epic-web/invariant": 1.0.0 + cross-spawn: 7.0.6 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crypto-js@4.2.0: {} + + crypto-random-string@2.0.0: {} + + crypto-random-string@4.0.0: + dependencies: + type-fest: 1.4.0 + + css-blank-pseudo@8.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + + css-declaration-sorter@7.3.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + css-functions-list@3.3.3: {} + + css-has-pseudo@8.0.0(postcss@8.5.6): + dependencies: + "@csstools/selector-specificity": 6.0.0(postcss-selector-parser@7.1.1) + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + postcss-value-parser: 4.2.0 + + css-prefers-color-scheme@11.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.1 + + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + css-what@6.2.2: {} + + cssdb@8.7.1: {} + + cssesc@3.0.0: {} + + cssnano-preset-default@7.0.10(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + css-declaration-sorter: 7.3.1(postcss@8.5.6) + cssnano-utils: 5.0.1(postcss@8.5.6) + postcss: 8.5.6 + postcss-calc: 10.1.1(postcss@8.5.6) + postcss-colormin: 7.0.5(postcss@8.5.6) + postcss-convert-values: 7.0.8(postcss@8.5.6) + postcss-discard-comments: 7.0.5(postcss@8.5.6) + postcss-discard-duplicates: 7.0.2(postcss@8.5.6) + postcss-discard-empty: 7.0.1(postcss@8.5.6) + postcss-discard-overridden: 7.0.1(postcss@8.5.6) + postcss-merge-longhand: 7.0.5(postcss@8.5.6) + postcss-merge-rules: 7.0.7(postcss@8.5.6) + postcss-minify-font-values: 7.0.1(postcss@8.5.6) + postcss-minify-gradients: 7.0.1(postcss@8.5.6) + postcss-minify-params: 7.0.5(postcss@8.5.6) + postcss-minify-selectors: 7.0.5(postcss@8.5.6) + postcss-normalize-charset: 7.0.1(postcss@8.5.6) + postcss-normalize-display-values: 7.0.1(postcss@8.5.6) + postcss-normalize-positions: 7.0.1(postcss@8.5.6) + postcss-normalize-repeat-style: 7.0.1(postcss@8.5.6) + postcss-normalize-string: 7.0.1(postcss@8.5.6) + postcss-normalize-timing-functions: 7.0.1(postcss@8.5.6) + postcss-normalize-unicode: 7.0.5(postcss@8.5.6) + postcss-normalize-url: 7.0.1(postcss@8.5.6) + postcss-normalize-whitespace: 7.0.1(postcss@8.5.6) + postcss-ordered-values: 7.0.2(postcss@8.5.6) + postcss-reduce-initial: 7.0.5(postcss@8.5.6) + postcss-reduce-transforms: 7.0.1(postcss@8.5.6) + postcss-svgo: 7.1.0(postcss@8.5.6) + postcss-unique-selectors: 7.0.4(postcss@8.5.6) + + cssnano-utils@5.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + cssnano@7.1.2(postcss@8.5.6): + dependencies: + cssnano-preset-default: 7.0.10(postcss@8.5.6) + lilconfig: 3.1.3 + postcss: 8.5.6 + + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + + cz-conventional-changelog@3.3.0(@types/node@24.7.0)(typescript@5.9.3): + dependencies: + chalk: 2.4.2 + commitizen: 4.3.1(@types/node@24.7.0)(typescript@5.9.3) + conventional-commit-types: 3.0.0 + lodash.map: 4.6.0 + longest: 2.0.1 + word-wrap: 1.2.5 + optionalDependencies: + "@commitlint/load": 20.4.0(@types/node@24.7.0)(typescript@5.9.3) + transitivePeerDependencies: + - "@types/node" + - typescript + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-dispatch@3.0.1: {} + + d3-ease@3.0.1: {} + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-geo-projection@4.0.0: + dependencies: + commander: 7.2.0 + d3-array: 3.2.4 + d3-geo: 3.1.1 + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-quadtree@3.0.1: {} + + d3-selection@3.0.0: {} + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + dargs@8.1.0: {} + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decamelize@1.2.0: {} + + decompress-response@10.0.0: + dependencies: + mimic-response: 4.0.0 + + dedent@0.7.0: {} + + deep-equal@1.1.2: + dependencies: + is-arguments: 1.2.0 + is-date-object: 1.1.0 + is-regex: 1.2.1 + object-is: 1.1.6 + object-keys: 1.1.1 + regexp.prototype.flags: 1.5.4 + + deep-extend@0.6.0: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-lazy-prop@3.0.0: {} + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + detect-file@1.0.0: {} + + detect-indent@6.1.0: {} + + detect-libc@2.1.2: {} + + dfa@1.2.0: {} + + didyoumean@1.2.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dlv@1.1.3: {} + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dot-prop@5.3.0: + dependencies: + is-obj: 2.0.0 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + duplexer2@0.1.4: + dependencies: + readable-stream: 2.3.8 + + ejs@3.1.10: + dependencies: + jake: 10.9.4 + + electron-to-chromium@1.5.286: {} + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + emojilib@2.4.0: {} + + entities@4.5.0: {} + + env-ci@11.2.0: + dependencies: + execa: 8.0.1 + java-properties: 1.0.2 + + env-paths@2.2.1: {} + + environment@1.1.0: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + error-stack-parser-es@1.0.5: {} + + es-abstract@1.24.1: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.20 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + esbuild@0.27.3: + optionalDependencies: + "@esbuild/aix-ppc64": 0.27.3 + "@esbuild/android-arm": 0.27.3 + "@esbuild/android-arm64": 0.27.3 + "@esbuild/android-x64": 0.27.3 + "@esbuild/darwin-arm64": 0.27.3 + "@esbuild/darwin-x64": 0.27.3 + "@esbuild/freebsd-arm64": 0.27.3 + "@esbuild/freebsd-x64": 0.27.3 + "@esbuild/linux-arm": 0.27.3 + "@esbuild/linux-arm64": 0.27.3 + "@esbuild/linux-ia32": 0.27.3 + "@esbuild/linux-loong64": 0.27.3 + "@esbuild/linux-mips64el": 0.27.3 + "@esbuild/linux-ppc64": 0.27.3 + "@esbuild/linux-riscv64": 0.27.3 + "@esbuild/linux-s390x": 0.27.3 + "@esbuild/linux-x64": 0.27.3 + "@esbuild/netbsd-arm64": 0.27.3 + "@esbuild/netbsd-x64": 0.27.3 + "@esbuild/openbsd-arm64": 0.27.3 + "@esbuild/openbsd-x64": 0.27.3 + "@esbuild/openharmony-arm64": 0.27.3 + "@esbuild/sunos-x64": 0.27.3 + "@esbuild/win32-arm64": 0.27.3 + "@esbuild/win32-ia32": 0.27.3 + "@esbuild/win32-x64": 0.27.3 + + escalade@3.2.0: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + eslint-config-prettier@10.1.8(eslint@10.0.0(jiti@1.21.7)): + dependencies: + eslint: 10.0.0(jiti@1.21.7) + + eslint-plugin-prettier@5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.0.0(jiti@1.21.7)))(eslint@10.0.0(jiti@1.21.7))(prettier@3.8.1): + dependencies: + eslint: 10.0.0(jiti@1.21.7) + prettier: 3.8.1 + prettier-linter-helpers: 1.0.1 + synckit: 0.11.12 + optionalDependencies: + "@types/eslint": 9.6.1 + eslint-config-prettier: 10.1.8(eslint@10.0.0(jiti@1.21.7)) + + eslint-scope@9.1.0: + dependencies: + "@types/esrecurse": 4.3.1 + "@types/estree": 1.0.8 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.0: {} + + eslint@10.0.0(jiti@1.21.7): + dependencies: + "@eslint-community/eslint-utils": 4.9.1(eslint@10.0.0(jiti@1.21.7)) + "@eslint-community/regexpp": 4.12.2 + "@eslint/config-array": 0.23.1 + "@eslint/config-helpers": 0.5.2 + "@eslint/core": 1.1.0 + "@eslint/plugin-kit": 0.6.0 + "@humanfs/node": 0.16.7 + "@humanwhocodes/module-importer": 1.0.1 + "@humanwhocodes/retry": 0.4.3 + "@types/estree": 1.0.8 + ajv: 6.12.6 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.0 + eslint-visitor-keys: 5.0.0 + espree: 11.1.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.1 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 1.21.7 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + espree@11.1.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 5.0.0 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@1.0.1: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + eventemitter3@5.0.4: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + execa@9.6.1: + dependencies: + "@sindresorhus/merge-streams": 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + + expand-tilde@2.0.2: + dependencies: + homedir-polyfill: 1.0.3 + + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + + fast-content-type-parse@3.0.0: {} + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + "@nodelib/fs.stat": 2.0.5 + "@nodelib/fs.walk": 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-uri@3.1.0: {} + + fastest-levenshtein@1.0.16: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + figures@2.0.0: + dependencies: + escape-string-regexp: 1.0.5 + + figures@3.2.0: + dependencies: + escape-string-regexp: 1.0.5 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + + file-entry-cache@11.1.2: + dependencies: + flat-cache: 6.1.20 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + filelist@1.0.4: + dependencies: + minimatch: 5.1.6 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-node-modules@2.1.3: + dependencies: + findup-sync: 4.0.0 + merge: 2.1.1 + + find-root@1.1.0: {} + + find-up-simple@1.0.1: {} + + find-up@2.1.0: + dependencies: + locate-path: 2.0.0 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + find-versions@6.0.0: + dependencies: + semver-regex: 4.0.5 + super-regex: 1.1.0 + + findup-sync@4.0.0: + dependencies: + detect-file: 1.0.0 + is-glob: 4.0.3 + micromatch: 4.0.8 + resolve-dir: 1.0.1 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flat-cache@6.1.20: + dependencies: + cacheable: 2.3.2 + flatted: 3.3.3 + hookified: 1.15.1 + + flatpickr@4.6.13: {} + + flatted@3.3.3: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data-encoder@4.1.0: {} + + formdata-node@6.0.3: {} + + fraction.js@5.3.4: {} + + from2@2.3.0: + dependencies: + inherits: 2.0.4 + readable-stream: 2.3.8 + + fs-extra@11.3.3: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs.realpath@1.0.0: {} + + fscreen@1.2.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function-timeout@1.0.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + fuse.js@7.1.0: {} + + generator-function@2.0.1: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.5.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-own-enumerable-property-symbols@3.0.2: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@6.0.1: {} + + get-stream@7.0.1: {} + + get-stream@8.0.1: {} + + get-stream@9.0.1: + dependencies: + "@sec-ant/readable-stream": 0.4.1 + is-stream: 4.0.1 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + git-log-parser@1.2.1: + dependencies: + argv-formatter: 1.0.0 + spawn-error-forwarder: 1.0.0 + split2: 1.0.0 + stream-combiner2: 1.1.1 + through2: 2.0.5 + traverse: 0.6.8 + + git-raw-commits@4.0.0: + dependencies: + dargs: 8.1.0 + meow: 12.1.1 + split2: 4.2.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@11.1.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.2.3 + minimatch: 10.2.1 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.1 + + glob@13.0.5: + dependencies: + minimatch: 10.2.1 + minipass: 7.1.3 + path-scurry: 2.0.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + global-directory@4.0.1: + dependencies: + ini: 4.1.1 + + global-modules@1.0.0: + dependencies: + global-prefix: 1.0.2 + is-windows: 1.0.2 + resolve-dir: 1.0.1 + + global-modules@2.0.0: + dependencies: + global-prefix: 3.0.0 + + global-prefix@1.0.2: + dependencies: + expand-tilde: 2.0.2 + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 1.0.2 + which: 1.3.1 + + global-prefix@3.0.0: + dependencies: + ini: 1.3.8 + kind-of: 6.0.3 + which: 1.3.1 + + globals@14.0.0: {} + + globals@17.3.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + globby@14.1.0: + dependencies: + "@sindresorhus/merge-streams": 2.3.0 + fast-glob: 3.3.3 + ignore: 7.0.5 + path-type: 6.0.0 + slash: 5.1.0 + unicorn-magic: 0.3.0 + + globby@16.1.1: + dependencies: + "@sindresorhus/merge-streams": 4.0.0 + fast-glob: 3.3.3 + ignore: 7.0.5 + is-path-inside: 4.0.0 + slash: 5.1.0 + unicorn-magic: 0.4.0 + + globjoin@0.1.4: {} + + gopd@1.2.0: {} + + got@14.6.6: + dependencies: + "@sindresorhus/is": 7.2.0 + byte-counter: 0.1.0 + cacheable-lookup: 7.0.0 + cacheable-request: 13.0.18 + decompress-response: 10.0.0 + form-data-encoder: 4.1.0 + http2-wrapper: 2.2.1 + keyv: 5.6.0 + lowercase-keys: 3.0.0 + p-cancelable: 4.0.1 + responselike: 4.0.2 + type-fest: 4.41.0 + + graceful-fs@4.2.10: {} + + graceful-fs@4.2.11: {} + + handlebars@4.7.8: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + has-bigints@1.1.0: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-flag@5.0.1: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hashery@1.5.0: + dependencies: + hookified: 1.15.1 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + highlight.js@10.7.3: {} + + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + + hook-std@4.0.0: {} + + hookified@1.15.1: {} + + hosted-git-info@7.0.2: + dependencies: + lru-cache: 10.4.3 + + hosted-git-info@9.0.2: + dependencies: + lru-cache: 11.2.6 + + hpagent@1.2.0: {} + + html-tags@5.1.0: {} + + htmlfy@1.0.1: {} + + http-cache-semantics@4.2.0: {} + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + http2-wrapper@2.2.1: + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + human-signals@2.1.0: {} + + human-signals@5.0.0: {} + + human-signals@8.0.1: {} + + husky@9.1.7: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + idb@7.1.1: {} + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-from-esm@2.0.0: + dependencies: + debug: 4.4.3 + import-meta-resolve: 4.2.0 + transitivePeerDependencies: + - supports-color + + import-meta-resolve@4.2.0: {} + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + indent-string@5.0.0: {} + + index-to-position@1.2.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@1.3.8: {} + + ini@4.1.1: {} + + inquirer@7.3.3: + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.23 + mute-stream: 0.0.8 + run-async: 2.4.1 + rxjs: 6.6.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + + inquirer@8.2.5: + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 0.0.8 + ora: 5.4.1 + run-async: 2.4.1 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + wrap-ansi: 7.0.0 + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + internmap@2.0.3: {} + + into-stream@7.0.0: + dependencies: + from2: 2.3.0 + p-is-promise: 3.0.0 + + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.2.1: {} + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-ci@4.1.0: + dependencies: + ci-info: 4.4.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-docker@3.0.0: {} + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.5.0 + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-interactive@1.0.0: {} + + is-map@2.0.3: {} + + is-module@1.0.0: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-obj@1.0.1: {} + + is-obj@2.0.0: {} + + is-path-inside@4.0.0: {} + + is-plain-obj@4.1.0: {} + + is-plain-object@5.0.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-regexp@1.0.0: {} + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@2.0.1: {} + + is-stream@3.0.0: {} + + is-stream@4.0.1: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + + is-unicode-supported@0.1.0: {} + + is-unicode-supported@2.1.0: {} + + is-utf8@0.2.1: {} + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-windows@1.0.2: {} + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + isarray@1.0.0: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + issue-parser@7.0.1: + dependencies: + lodash.capitalize: 4.2.1 + lodash.escaperegexp: 4.1.2 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.uniqby: 4.7.0 + + jackspeak@4.2.3: + dependencies: + "@isaacs/cliui": 9.0.0 + + jake@10.9.4: + dependencies: + async: 3.2.6 + filelist: 1.0.4 + picocolors: 1.1.1 + + java-properties@1.0.2: {} + + jiti@1.21.7: {} + + jiti@2.6.1: {} + + jpeg-exif@1.1.4: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-fixer@1.6.15: + dependencies: + "@babel/runtime": 7.28.6 + chalk: 4.1.2 + pegjs: 0.10.0 + + json-parse-better-errors@1.0.2: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-schema@0.4.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsonpointer@5.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + keyv@5.6.0: + dependencies: + "@keyv/serialize": 1.1.1 + + kind-of@6.0.3: {} + + known-css-properties@0.37.0: {} + + leaflet.markercluster@1.5.3(leaflet@1.9.4): + dependencies: + leaflet: 1.9.4 + + leaflet@1.9.4: {} + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + lint-staged@16.2.7: + dependencies: + commander: 14.0.3 + listr2: 9.0.5 + micromatch: 4.0.8 + nano-spawn: 2.0.0 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.8.2 + + listr2@9.0.5: + dependencies: + cli-truncate: 5.1.1 + colorette: 2.0.20 + eventemitter3: 5.0.4 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.2 + + lit-element@4.2.2: + dependencies: + "@lit-labs/ssr-dom-shim": 1.5.1 + "@lit/reactive-element": 2.1.2 + lit-html: 3.3.2 + + lit-html@3.3.2: + dependencies: + "@types/trusted-types": 2.0.7 + + lit@3.3.2: + dependencies: + "@lit/reactive-element": 2.1.2 + lit-element: 4.2.2 + lit-html: 3.3.2 + + load-json-file@4.0.0: + dependencies: + graceful-fs: 4.2.11 + parse-json: 4.0.0 + pify: 3.0.0 + strip-bom: 3.0.0 + + locate-path@2.0.0: + dependencies: + p-locate: 2.0.0 + path-exists: 3.0.0 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash-es@4.17.23: {} + + lodash.camelcase@4.3.0: {} + + lodash.capitalize@4.2.1: {} + + lodash.debounce@4.0.8: {} + + lodash.escaperegexp@4.1.2: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.kebabcase@4.1.1: {} + + lodash.map@4.6.0: {} + + lodash.memoize@4.1.2: {} + + lodash.mergewith@4.6.2: {} + + lodash.snakecase@4.1.1: {} + + lodash.sortby@4.7.0: {} + + lodash.startcase@4.4.0: {} + + lodash.truncate@4.4.2: {} + + lodash.uniq@4.5.0: {} + + lodash.uniqby@4.7.0: {} + + lodash.upperfirst@4.3.1: {} + + lodash@4.17.21: {} + + lodash@4.17.23: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.3.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.1.2 + wrap-ansi: 9.0.2 + + longest@2.0.1: {} + + lowercase-keys@3.0.0: {} + + lru-cache@10.4.3: {} + + lru-cache@11.2.6: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.25.9: + dependencies: + sourcemap-codec: 1.4.8 + + make-asynchronous@1.0.1: + dependencies: + p-event: 6.0.1 + type-fest: 4.41.0 + web-worker: 1.2.0 + + marked-terminal@7.3.0(marked@15.0.12): + dependencies: + ansi-escapes: 7.3.0 + ansi-regex: 6.2.2 + chalk: 5.6.2 + cli-highlight: 2.1.11 + cli-table3: 0.6.5 + marked: 15.0.12 + node-emoji: 2.2.0 + supports-hyperlinks: 3.2.0 + + marked@15.0.12: {} + + marked@17.0.3: {} + + math-intrinsics@1.1.0: {} + + mathml-tag-names@4.0.0: {} + + mdn-data@2.0.28: {} + + mdn-data@2.12.2: {} + + meow@12.1.1: {} + + meow@13.2.0: {} + + meow@14.0.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + merge@2.1.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime@4.1.0: {} + + mimic-fn@2.1.0: {} + + mimic-fn@4.0.0: {} + + mimic-function@5.0.1: {} + + mimic-response@4.0.0: {} + + mini-svg-data-uri@1.4.4: {} + + minimatch@10.2.1: + dependencies: + brace-expansion: 5.0.2 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.7: {} + + minimist@1.2.8: {} + + minipass@7.1.3: {} + + mitt@3.0.1: {} + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + mute-stream@0.0.8: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nano-spawn@2.0.0: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + neo-async@2.6.2: {} + + nerf-dart@1.0.0: {} + + node-emoji@2.2.0: + dependencies: + "@sindresorhus/is": 4.6.0 + char-regex: 1.0.2 + emojilib: 2.4.0 + skin-tone: 2.0.0 + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-releases@2.0.27: {} + + normalize-package-data@6.0.2: + dependencies: + hosted-git-info: 7.0.2 + semver: 7.7.4 + validate-npm-package-license: 3.0.4 + + normalize-package-data@8.0.0: + dependencies: + hosted-git-info: 9.0.2 + semver: 7.7.4 + validate-npm-package-license: 3.0.4 + + normalize-path@3.0.0: {} + + normalize-url@8.1.1: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + + npm@11.10.0: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.4: {} + + object-is@1.1.6: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + ohash@2.0.11: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + open@10.2.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + + os-tmpdir@1.0.2: {} + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-cancelable@4.0.1: {} + + p-each-series@3.0.0: {} + + p-event@6.0.1: + dependencies: + p-timeout: 6.1.4 + + p-filter@4.1.0: + dependencies: + p-map: 7.0.4 + + p-is-promise@3.0.0: {} + + p-limit@1.3.0: + dependencies: + p-try: 1.0.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@2.0.0: + dependencies: + p-limit: 1.3.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map@7.0.4: {} + + p-reduce@2.1.0: {} + + p-reduce@3.0.0: {} + + p-timeout@6.1.4: {} + + p-try@1.0.0: {} + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + pako@0.2.9: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@4.0.0: + dependencies: + error-ex: 1.3.4 + json-parse-better-errors: 1.0.2 + + parse-json@5.2.0: + dependencies: + "@babel/code-frame": 7.29.0 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-json@8.3.0: + dependencies: + "@babel/code-frame": 7.29.0 + index-to-position: 1.2.0 + type-fest: 4.41.0 + + parse-ms@4.0.0: {} + + parse-passwd@1.0.0: {} + + parse-path@7.1.0: + dependencies: + protocols: 2.0.2 + + parse-url@10.0.3: + dependencies: + parse-path: 7.1.0 + + parse5-htmlparser2-tree-adapter@6.0.1: + dependencies: + parse5: 6.0.1 + + parse5@5.1.1: {} + + parse5@6.0.1: {} + + path-exists@3.0.0: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-parse@1.0.7: {} + + path-scurry@2.0.1: + dependencies: + lru-cache: 11.2.6 + minipass: 7.1.3 + + path-type@4.0.0: {} + + path-type@6.0.0: {} + + pathe@2.0.3: {} + + pdfmake@0.2.23: + dependencies: + "@foliojs-fork/linebreak": 1.1.2 + "@foliojs-fork/pdfkit": 0.15.3 + iconv-lite: 0.7.2 + xmldoc: 2.0.3 + + pegjs@0.10.0: {} + + perfect-debounce@2.1.0: {} + + performance-now@2.1.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pidtree@0.6.0: {} + + pify@2.3.0: {} + + pify@3.0.0: {} + + pify@5.0.0: {} + + pirates@4.0.7: {} + + pkg-conf@2.1.0: + dependencies: + find-up: 2.1.0 + load-json-file: 4.0.0 + + png-js@1.0.0: {} + + polylabel@1.1.0: + dependencies: + tinyqueue: 2.0.3 + + possible-typed-array-names@1.1.0: {} + + postcss-attribute-case-insensitive@8.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + + postcss-calc@10.1.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + postcss-value-parser: 4.2.0 + + postcss-clamp@4.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-color-functional-notation@8.0.1(postcss@8.5.6): + dependencies: + "@csstools/css-color-parser": 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/postcss-progressive-custom-properties": 5.0.0(postcss@8.5.6) + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + + postcss-color-hex-alpha@11.0.0(postcss@8.5.6): + dependencies: + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-color-rebeccapurple@11.0.0(postcss@8.5.6): + dependencies: + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-colormin@7.0.5(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + caniuse-api: 3.0.0 + colord: 2.9.3 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-convert-values@7.0.8(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-custom-media@12.0.0(postcss@8.5.6): + dependencies: + "@csstools/cascade-layer-name-parser": 3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/media-query-list-parser": 5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + postcss: 8.5.6 + + postcss-custom-properties@15.0.0(postcss@8.5.6): + dependencies: + "@csstools/cascade-layer-name-parser": 3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-custom-selectors@9.0.0(postcss@8.5.6): + dependencies: + "@csstools/cascade-layer-name-parser": 3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + + postcss-dir-pseudo-class@10.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + + postcss-discard-comments@7.0.5(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + + postcss-discard-duplicates@7.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-discard-empty@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-discard-overridden@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-double-position-gradients@7.0.0(postcss@8.5.6): + dependencies: + "@csstools/postcss-progressive-custom-properties": 5.0.0(postcss@8.5.6) + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-focus-visible@11.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + + postcss-focus-within@10.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + + postcss-font-variant@5.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-gap-properties@7.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-image-set-function@8.0.0(postcss@8.5.6): + dependencies: + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-import@15.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.11 + + postcss-import@16.1.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.11 + + postcss-js@4.1.0(postcss@8.5.6): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.6 + + postcss-lab-function@8.0.1(postcss@8.5.6): + dependencies: + "@csstools/css-color-parser": 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-tokenizer": 4.0.0 + "@csstools/postcss-progressive-custom-properties": 5.0.0(postcss@8.5.6) + "@csstools/utilities": 3.0.0(postcss@8.5.6) + postcss: 8.5.6 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.2): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.6 + yaml: 2.8.2 + + postcss-logical@9.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-merge-longhand@7.0.5(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + stylehacks: 7.0.7(postcss@8.5.6) + + postcss-merge-rules@7.0.7(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + caniuse-api: 3.0.0 + cssnano-utils: 5.0.1(postcss@8.5.6) + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + + postcss-minify-font-values@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-minify-gradients@7.0.1(postcss@8.5.6): + dependencies: + colord: 2.9.3 + cssnano-utils: 5.0.1(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-minify-params@7.0.5(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + cssnano-utils: 5.0.1(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-minify-selectors@7.0.5(postcss@8.5.6): + dependencies: + cssesc: 3.0.0 + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + + postcss-nested@6.2.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-nesting@14.0.0(postcss@8.5.6): + dependencies: + "@csstools/selector-resolve-nested": 4.0.0(postcss-selector-parser@7.1.1) + "@csstools/selector-specificity": 6.0.0(postcss-selector-parser@7.1.1) + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + + postcss-normalize-charset@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-normalize-display-values@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-normalize-positions@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-normalize-repeat-style@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-normalize-string@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-normalize-timing-functions@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-normalize-unicode@7.0.5(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-normalize-url@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-normalize-whitespace@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-opacity-percentage@3.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-ordered-values@7.0.2(postcss@8.5.6): + dependencies: + cssnano-utils: 5.0.1(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-overflow-shorthand@7.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-page-break@3.0.4(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-place@11.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-preset-env@11.1.3(postcss@8.5.6): + dependencies: + "@csstools/postcss-alpha-function": 2.0.2(postcss@8.5.6) + "@csstools/postcss-cascade-layers": 6.0.0(postcss@8.5.6) + "@csstools/postcss-color-function": 5.0.1(postcss@8.5.6) + "@csstools/postcss-color-function-display-p3-linear": 2.0.1(postcss@8.5.6) + "@csstools/postcss-color-mix-function": 4.0.1(postcss@8.5.6) + "@csstools/postcss-color-mix-variadic-function-arguments": 2.0.1(postcss@8.5.6) + "@csstools/postcss-content-alt-text": 3.0.0(postcss@8.5.6) + "@csstools/postcss-contrast-color-function": 3.0.1(postcss@8.5.6) + "@csstools/postcss-exponential-functions": 3.0.0(postcss@8.5.6) + "@csstools/postcss-font-format-keywords": 5.0.0(postcss@8.5.6) + "@csstools/postcss-gamut-mapping": 3.0.1(postcss@8.5.6) + "@csstools/postcss-gradients-interpolation-method": 6.0.1(postcss@8.5.6) + "@csstools/postcss-hwb-function": 5.0.1(postcss@8.5.6) + "@csstools/postcss-ic-unit": 5.0.0(postcss@8.5.6) + "@csstools/postcss-initial": 3.0.0(postcss@8.5.6) + "@csstools/postcss-is-pseudo-class": 6.0.0(postcss@8.5.6) + "@csstools/postcss-light-dark-function": 3.0.0(postcss@8.5.6) + "@csstools/postcss-logical-float-and-clear": 4.0.0(postcss@8.5.6) + "@csstools/postcss-logical-overflow": 3.0.0(postcss@8.5.6) + "@csstools/postcss-logical-overscroll-behavior": 3.0.0(postcss@8.5.6) + "@csstools/postcss-logical-resize": 4.0.0(postcss@8.5.6) + "@csstools/postcss-logical-viewport-units": 4.0.0(postcss@8.5.6) + "@csstools/postcss-media-minmax": 3.0.0(postcss@8.5.6) + "@csstools/postcss-media-queries-aspect-ratio-number-values": 4.0.0(postcss@8.5.6) + "@csstools/postcss-mixins": 1.0.0(postcss@8.5.6) + "@csstools/postcss-nested-calc": 5.0.0(postcss@8.5.6) + "@csstools/postcss-normalize-display-values": 5.0.1(postcss@8.5.6) + "@csstools/postcss-oklab-function": 5.0.1(postcss@8.5.6) + "@csstools/postcss-position-area-property": 2.0.0(postcss@8.5.6) + "@csstools/postcss-progressive-custom-properties": 5.0.0(postcss@8.5.6) + "@csstools/postcss-property-rule-prelude-list": 2.0.0(postcss@8.5.6) + "@csstools/postcss-random-function": 3.0.0(postcss@8.5.6) + "@csstools/postcss-relative-color-syntax": 4.0.1(postcss@8.5.6) + "@csstools/postcss-scope-pseudo-class": 5.0.0(postcss@8.5.6) + "@csstools/postcss-sign-functions": 2.0.0(postcss@8.5.6) + "@csstools/postcss-stepped-value-functions": 5.0.0(postcss@8.5.6) + "@csstools/postcss-syntax-descriptor-syntax-production": 2.0.0(postcss@8.5.6) + "@csstools/postcss-system-ui-font-family": 2.0.0(postcss@8.5.6) + "@csstools/postcss-text-decoration-shorthand": 5.0.2(postcss@8.5.6) + "@csstools/postcss-trigonometric-functions": 5.0.0(postcss@8.5.6) + "@csstools/postcss-unset-value": 5.0.0(postcss@8.5.6) + autoprefixer: 10.4.24(postcss@8.5.6) + browserslist: 4.28.1 + css-blank-pseudo: 8.0.1(postcss@8.5.6) + css-has-pseudo: 8.0.0(postcss@8.5.6) + css-prefers-color-scheme: 11.0.0(postcss@8.5.6) + cssdb: 8.7.1 + postcss: 8.5.6 + postcss-attribute-case-insensitive: 8.0.0(postcss@8.5.6) + postcss-clamp: 4.1.0(postcss@8.5.6) + postcss-color-functional-notation: 8.0.1(postcss@8.5.6) + postcss-color-hex-alpha: 11.0.0(postcss@8.5.6) + postcss-color-rebeccapurple: 11.0.0(postcss@8.5.6) + postcss-custom-media: 12.0.0(postcss@8.5.6) + postcss-custom-properties: 15.0.0(postcss@8.5.6) + postcss-custom-selectors: 9.0.0(postcss@8.5.6) + postcss-dir-pseudo-class: 10.0.0(postcss@8.5.6) + postcss-double-position-gradients: 7.0.0(postcss@8.5.6) + postcss-focus-visible: 11.0.0(postcss@8.5.6) + postcss-focus-within: 10.0.0(postcss@8.5.6) + postcss-font-variant: 5.0.0(postcss@8.5.6) + postcss-gap-properties: 7.0.0(postcss@8.5.6) + postcss-image-set-function: 8.0.0(postcss@8.5.6) + postcss-lab-function: 8.0.1(postcss@8.5.6) + postcss-logical: 9.0.0(postcss@8.5.6) + postcss-nesting: 14.0.0(postcss@8.5.6) + postcss-opacity-percentage: 3.0.0(postcss@8.5.6) + postcss-overflow-shorthand: 7.0.0(postcss@8.5.6) + postcss-page-break: 3.0.4(postcss@8.5.6) + postcss-place: 11.0.0(postcss@8.5.6) + postcss-pseudo-class-any-link: 11.0.0(postcss@8.5.6) + postcss-replace-overflow-wrap: 4.0.0(postcss@8.5.6) + postcss-selector-not: 9.0.0(postcss@8.5.6) + + postcss-pseudo-class-any-link@11.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + + postcss-reduce-initial@7.0.5(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + caniuse-api: 3.0.0 + postcss: 8.5.6 + + postcss-reduce-transforms@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-replace-overflow-wrap@4.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-reporter@7.1.0(postcss@8.5.6): + dependencies: + picocolors: 1.1.1 + postcss: 8.5.6 + thenby: 1.3.4 + + postcss-safe-parser@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-selector-not@9.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-selector-parser@7.1.1: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-svgo@7.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + svgo: 4.0.0 + + postcss-unique-selectors@7.0.4(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.1: + dependencies: + fast-diff: 1.3.0 + + prettier-plugin-organize-imports@4.3.0(prettier@3.8.1)(typescript@5.9.3): + dependencies: + prettier: 3.8.1 + typescript: 5.9.3 + + prettier@2.8.8: + optional: true + + prettier@3.8.1: {} + + pretty-bytes@5.6.0: {} + + pretty-bytes@6.1.1: {} + + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + + process-nextick-args@2.0.1: {} + + proto-list@1.2.4: {} + + protocols@2.0.2: {} + + punycode@2.3.1: {} + + qified@0.6.0: + dependencies: + hookified: 1.15.1 + + queue-microtask@1.2.3: {} + + quick-lru@5.1.1: {} + + raf@3.4.1: + dependencies: + performance-now: 2.1.0 + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + read-package-up@11.0.0: + dependencies: + find-up-simple: 1.0.1 + read-pkg: 9.0.1 + type-fest: 4.41.0 + + read-package-up@12.0.0: + dependencies: + find-up-simple: 1.0.1 + read-pkg: 10.1.0 + type-fest: 5.4.4 + + read-pkg@10.1.0: + dependencies: + "@types/normalize-package-data": 2.4.4 + normalize-package-data: 8.0.0 + parse-json: 8.3.0 + type-fest: 5.4.4 + unicorn-magic: 0.4.0 + + read-pkg@9.0.1: + dependencies: + "@types/normalize-package-data": 2.4.4 + normalize-package-data: 6.0.2 + parse-json: 8.3.0 + type-fest: 4.41.0 + unicorn-magic: 0.1.0 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regenerate-unicode-properties@10.2.2: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + regexpu-core@6.4.0: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.2 + regjsgen: 0.8.0 + regjsparser: 0.13.0 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.1 + + registry-auth-token@5.1.1: + dependencies: + "@pnpm/npm-conf": 3.0.2 + + regjsgen@0.8.0: {} + + regjsparser@0.13.0: + dependencies: + jsesc: 3.1.0 + + regression@2.0.1: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + require-main-filename@2.0.0: {} + + resolve-alpn@1.2.1: {} + + resolve-dir@1.0.1: + dependencies: + expand-tilde: 2.0.2 + global-modules: 1.0.0 + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + responselike@4.0.2: + dependencies: + lowercase-keys: 3.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rgbcolor@1.0.1: {} + + rollup@2.79.2: + optionalDependencies: + fsevents: 2.3.3 + + rollup@4.57.1: + dependencies: + "@types/estree": 1.0.8 + optionalDependencies: + "@rollup/rollup-android-arm-eabi": 4.57.1 + "@rollup/rollup-android-arm64": 4.57.1 + "@rollup/rollup-darwin-arm64": 4.57.1 + "@rollup/rollup-darwin-x64": 4.57.1 + "@rollup/rollup-freebsd-arm64": 4.57.1 + "@rollup/rollup-freebsd-x64": 4.57.1 + "@rollup/rollup-linux-arm-gnueabihf": 4.57.1 + "@rollup/rollup-linux-arm-musleabihf": 4.57.1 + "@rollup/rollup-linux-arm64-gnu": 4.57.1 + "@rollup/rollup-linux-arm64-musl": 4.57.1 + "@rollup/rollup-linux-loong64-gnu": 4.57.1 + "@rollup/rollup-linux-loong64-musl": 4.57.1 + "@rollup/rollup-linux-ppc64-gnu": 4.57.1 + "@rollup/rollup-linux-ppc64-musl": 4.57.1 + "@rollup/rollup-linux-riscv64-gnu": 4.57.1 + "@rollup/rollup-linux-riscv64-musl": 4.57.1 + "@rollup/rollup-linux-s390x-gnu": 4.57.1 + "@rollup/rollup-linux-x64-gnu": 4.57.1 + "@rollup/rollup-linux-x64-musl": 4.57.1 + "@rollup/rollup-openbsd-x64": 4.57.1 + "@rollup/rollup-openharmony-arm64": 4.57.1 + "@rollup/rollup-win32-arm64-msvc": 4.57.1 + "@rollup/rollup-win32-ia32-msvc": 4.57.1 + "@rollup/rollup-win32-x64-gnu": 4.57.1 + "@rollup/rollup-win32-x64-msvc": 4.57.1 + fsevents: 2.3.3 + + run-applescript@7.1.0: {} + + run-async@2.4.1: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@6.6.7: + dependencies: + tslib: 1.14.1 + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + + sax@1.4.4: {} + + semantic-release@25.0.3(typescript@5.9.3): + dependencies: + "@semantic-release/commit-analyzer": 13.0.1(semantic-release@25.0.3(typescript@5.9.3)) + "@semantic-release/error": 4.0.0 + "@semantic-release/github": 12.0.6(semantic-release@25.0.3(typescript@5.9.3)) + "@semantic-release/npm": 13.1.4(semantic-release@25.0.3(typescript@5.9.3)) + "@semantic-release/release-notes-generator": 14.1.0(semantic-release@25.0.3(typescript@5.9.3)) + aggregate-error: 5.0.0 + cosmiconfig: 9.0.0(typescript@5.9.3) + debug: 4.4.3 + env-ci: 11.2.0 + execa: 9.6.1 + figures: 6.1.0 + find-versions: 6.0.0 + get-stream: 6.0.1 + git-log-parser: 1.2.1 + hook-std: 4.0.0 + hosted-git-info: 9.0.2 + import-from-esm: 2.0.0 + lodash-es: 4.17.23 + marked: 15.0.12 + marked-terminal: 7.3.0(marked@15.0.12) + micromatch: 4.0.8 + p-each-series: 3.0.0 + p-reduce: 3.0.0 + read-package-up: 12.0.0 + resolve-from: 5.0.0 + semver: 7.7.4 + signale: 1.4.0 + yargs: 18.0.0 + transitivePeerDependencies: + - supports-color + - typescript + + semver-regex@4.0.5: {} + + semver@6.3.1: {} + + semver@7.7.4: {} + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + set-blocking@2.0.0: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + sharp@0.34.5: + dependencies: + "@img/colour": 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + "@img/sharp-darwin-arm64": 0.34.5 + "@img/sharp-darwin-x64": 0.34.5 + "@img/sharp-libvips-darwin-arm64": 1.2.4 + "@img/sharp-libvips-darwin-x64": 1.2.4 + "@img/sharp-libvips-linux-arm": 1.2.4 + "@img/sharp-libvips-linux-arm64": 1.2.4 + "@img/sharp-libvips-linux-ppc64": 1.2.4 + "@img/sharp-libvips-linux-riscv64": 1.2.4 + "@img/sharp-libvips-linux-s390x": 1.2.4 + "@img/sharp-libvips-linux-x64": 1.2.4 + "@img/sharp-libvips-linuxmusl-arm64": 1.2.4 + "@img/sharp-libvips-linuxmusl-x64": 1.2.4 + "@img/sharp-linux-arm": 0.34.5 + "@img/sharp-linux-arm64": 0.34.5 + "@img/sharp-linux-ppc64": 0.34.5 + "@img/sharp-linux-riscv64": 0.34.5 + "@img/sharp-linux-s390x": 0.34.5 + "@img/sharp-linux-x64": 0.34.5 + "@img/sharp-linuxmusl-arm64": 0.34.5 + "@img/sharp-linuxmusl-x64": 0.34.5 + "@img/sharp-wasm32": 0.34.5 + "@img/sharp-win32-arm64": 0.34.5 + "@img/sharp-win32-ia32": 0.34.5 + "@img/sharp-win32-x64": 0.34.5 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + signale@1.4.0: + dependencies: + chalk: 2.4.2 + figures: 2.0.0 + pkg-conf: 2.1.0 + + sirv@3.0.2: + dependencies: + "@polka/url": 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + skin-tone@2.0.0: + dependencies: + unicode-emoji-modifier-base: 1.0.0 + + slash@5.1.0: {} + + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + smob@1.6.1: {} + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + + sourcemap-codec@1.4.8: {} + + spawn-error-forwarder@1.0.0: {} + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.22 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.22 + + spdx-license-ids@3.0.22: {} + + split2@1.0.0: + dependencies: + through2: 2.0.5 + + split2@4.2.0: {} + + stackblur-canvas@2.7.0: {} + + stencil-wormhole@3.4.1: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + stream-combiner2@1.1.1: + dependencies: + duplexer2: 0.1.4 + readable-stream: 2.3.8 + + string-argv@0.3.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.1.2 + + string-width@8.2.0: + dependencies: + get-east-asian-width: 1.5.0 + strip-ansi: 7.1.2 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + stringify-object@3.3.0: + dependencies: + get-own-enumerable-property-symbols: 3.0.2 + is-obj: 1.0.1 + is-regexp: 1.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-bom@3.0.0: {} + + strip-bom@4.0.0: {} + + strip-comments@2.0.1: {} + + strip-final-newline@2.0.0: {} + + strip-final-newline@3.0.0: {} + + strip-final-newline@4.0.0: {} + + strip-json-comments@2.0.1: {} + + strip-json-comments@3.1.1: {} + + style-mod@4.1.3: {} + + stylehacks@7.0.7(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + + stylelint-config-recommended@18.0.0(stylelint@17.3.0(typescript@5.9.3)): + dependencies: + stylelint: 17.3.0(typescript@5.9.3) + + stylelint-config-standard@40.0.0(stylelint@17.3.0(typescript@5.9.3)): + dependencies: + stylelint: 17.3.0(typescript@5.9.3) + stylelint-config-recommended: 18.0.0(stylelint@17.3.0(typescript@5.9.3)) + + stylelint@17.3.0(typescript@5.9.3): + dependencies: + "@csstools/css-calc": 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/css-parser-algorithms": 4.0.0(@csstools/css-tokenizer@4.0.0) + "@csstools/css-syntax-patches-for-csstree": 1.0.27 + "@csstools/css-tokenizer": 4.0.0 + "@csstools/media-query-list-parser": 5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + "@csstools/selector-resolve-nested": 4.0.0(postcss-selector-parser@7.1.1) + "@csstools/selector-specificity": 6.0.0(postcss-selector-parser@7.1.1) + balanced-match: 3.0.1 + colord: 2.9.3 + cosmiconfig: 9.0.0(typescript@5.9.3) + css-functions-list: 3.3.3 + css-tree: 3.1.0 + debug: 4.4.3 + fast-glob: 3.3.3 + fastest-levenshtein: 1.0.16 + file-entry-cache: 11.1.2 + global-modules: 2.0.0 + globby: 16.1.1 + globjoin: 0.1.4 + html-tags: 5.1.0 + ignore: 7.0.5 + import-meta-resolve: 4.2.0 + imurmurhash: 0.1.4 + is-plain-object: 5.0.0 + known-css-properties: 0.37.0 + mathml-tag-names: 4.0.0 + meow: 14.0.0 + micromatch: 4.0.8 + normalize-path: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-safe-parser: 7.0.1(postcss@8.5.6) + postcss-selector-parser: 7.1.1 + postcss-value-parser: 4.2.0 + string-width: 8.2.0 + supports-hyperlinks: 4.4.0 + svg-tags: 1.0.0 + table: 6.9.0 + write-file-atomic: 7.0.0 + transitivePeerDependencies: + - supports-color + - typescript + + sucrase@3.35.1: + dependencies: + "@jridgewell/gen-mapping": 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + + super-regex@1.1.0: + dependencies: + function-timeout: 1.0.2 + make-asynchronous: 1.0.1 + time-span: 5.1.0 + + supports-color@10.2.2: {} + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-hyperlinks@3.2.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + + supports-hyperlinks@4.4.0: + dependencies: + has-flag: 5.0.1 + supports-color: 10.2.2 + + supports-preserve-symlinks-flag@1.0.0: {} + + svg-tags@1.0.0: {} + + svgo@4.0.0: + dependencies: + commander: 11.1.0 + css-select: 5.2.2 + css-tree: 3.1.0 + css-what: 6.2.2 + csso: 5.0.5 + picocolors: 1.1.1 + sax: 1.4.4 + + synckit@0.11.12: + dependencies: + "@pkgr/core": 0.2.9 + + table@6.9.0: + dependencies: + ajv: 8.18.0 + lodash.truncate: 4.4.2 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + tagged-tag@1.0.0: {} + + tailwindcss@3.4.19(yaml@2.8.2): + dependencies: + "@alloc/quick-lru": 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-import: 15.1.0(postcss@8.5.6) + postcss-js: 4.1.0(postcss@8.5.6) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.2) + postcss-nested: 6.2.0(postcss@8.5.6) + postcss-selector-parser: 6.1.2 + resolve: 1.22.11 + sucrase: 3.35.1 + transitivePeerDependencies: + - tsx + - yaml + + temp-dir@2.0.0: {} + + temp-dir@3.0.0: {} + + tempy@0.6.0: + dependencies: + is-stream: 2.0.1 + temp-dir: 2.0.0 + type-fest: 0.16.0 + unique-string: 2.0.0 + + tempy@3.2.0: + dependencies: + is-stream: 3.0.0 + temp-dir: 3.0.0 + type-fest: 2.19.0 + unique-string: 3.0.0 + + terser@5.46.0: + dependencies: + "@jridgewell/source-map": 0.3.11 + acorn: 8.15.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + thenby@1.3.4: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + through2@2.0.5: + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + + through@2.3.8: {} + + time-span@5.1.0: + dependencies: + convert-hrtime: 5.0.0 + + tiny-inflate@1.0.3: {} + + tinyexec@1.0.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyqueue@2.0.3: {} + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + totalist@3.0.1: {} + + tr46@0.0.3: {} + + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + + traverse@0.6.8: {} + + ts-api-utils@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-interface-checker@0.1.13: {} + + tslib@1.14.1: {} + + tslib@2.8.1: {} + + tunnel@0.0.6: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.16.0: {} + + type-fest@0.21.3: {} + + type-fest@1.4.0: {} + + type-fest@2.19.0: {} + + type-fest@4.41.0: {} + + type-fest@5.4.4: + dependencies: + tagged-tag: 1.0.0 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript-eslint@8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3): + dependencies: + "@typescript-eslint/eslint-plugin": 8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3) + "@typescript-eslint/parser": 8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3) + "@typescript-eslint/typescript-estree": 8.56.0(typescript@5.9.3) + "@typescript-eslint/utils": 8.56.0(eslint@10.0.0(jiti@1.21.7))(typescript@5.9.3) + eslint: 10.0.0(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + uglify-js@3.19.3: + optional: true + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@7.14.0: {} + + undici@6.23.0: {} + + undici@7.22.0: {} + + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-emoji-modifier-base@1.0.0: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.2.0 + + unicode-match-property-value-ecmascript@2.2.1: {} + + unicode-properties@1.4.1: + dependencies: + base64-js: 1.5.1 + unicode-trie: 2.0.0 + + unicode-property-aliases-ecmascript@2.2.0: {} + + unicode-trie@2.0.0: + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + + unicorn-magic@0.1.0: {} + + unicorn-magic@0.3.0: {} + + unicorn-magic@0.4.0: {} + + unique-string@2.0.0: + dependencies: + crypto-random-string: 2.0.0 + + unique-string@3.0.0: + dependencies: + crypto-random-string: 4.0.0 + + universal-user-agent@7.0.3: {} + + universalify@2.0.1: {} + + unplugin-utils@0.3.1: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.3 + + upath@1.2.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + url-join@4.0.1: {} + + url-join@5.0.0: {} + + util-deprecate@1.0.2: {} + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + vite-dev-rpc@1.1.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)): + dependencies: + birpc: 2.9.0 + vite: 7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2) + vite-hot-client: 2.1.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)) + + vite-hot-client@2.1.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)): + dependencies: + vite: 7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2) + + vite-plugin-codeigniter@2.0.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)): + dependencies: + glob: 11.1.0 + picocolors: 1.1.1 + sharp: 0.34.5 + vite: 7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2) + vite-plugin-static-copy: 3.2.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)) + zod: 4.3.6 + + vite-plugin-inspect@11.3.3(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)): + dependencies: + ansis: 4.2.0 + debug: 4.4.3 + error-stack-parser-es: 1.0.5 + ohash: 2.0.11 + open: 10.2.0 + perfect-debounce: 2.1.0 + sirv: 3.0.2 + unplugin-utils: 0.3.1 + vite: 7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2) + vite-dev-rpc: 1.1.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)) + transitivePeerDependencies: + - supports-color + + vite-plugin-pwa@1.2.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2))(workbox-build@7.4.0)(workbox-window@7.4.0): + dependencies: + debug: 4.4.3 + pretty-bytes: 6.1.1 + tinyglobby: 0.2.15 + vite: 7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2) + workbox-build: 7.4.0 + workbox-window: 7.4.0 + transitivePeerDependencies: + - supports-color + + vite-plugin-static-copy@3.2.0(vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2)): + dependencies: + chokidar: 3.6.0 + p-map: 7.0.4 + picocolors: 1.1.1 + tinyglobby: 0.2.15 + vite: 7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2) + + vite@7.3.1(@types/node@24.7.0)(jiti@1.21.7)(terser@5.46.0)(yaml@2.8.2): + dependencies: + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.57.1 + tinyglobby: 0.2.15 + optionalDependencies: + "@types/node": 24.7.0 + fsevents: 2.3.3 + jiti: 1.21.7 + terser: 5.46.0 + yaml: 2.8.2 + + w3c-keyname@2.2.8: {} + + wavesurfer.js@7.12.1: {} + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + web-worker@1.2.0: {} + + webidl-conversions@3.0.1: {} + + webidl-conversions@4.0.2: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.20 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-module@2.0.1: {} + + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@1.3.1: + dependencies: + isexe: 2.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wordwrap@1.0.0: {} + + workbox-background-sync@7.4.0: + dependencies: + idb: 7.1.1 + workbox-core: 7.4.0 + + workbox-broadcast-update@7.4.0: + dependencies: + workbox-core: 7.4.0 + + workbox-build@7.4.0: + dependencies: + "@apideck/better-ajv-errors": 0.3.6(ajv@8.18.0) + "@babel/core": 7.29.0 + "@babel/preset-env": 7.29.0(@babel/core@7.29.0) + "@babel/runtime": 7.28.6 + "@rollup/plugin-babel": 5.3.1(@babel/core@7.29.0)(rollup@2.79.2) + "@rollup/plugin-node-resolve": 15.3.1(rollup@2.79.2) + "@rollup/plugin-replace": 2.4.2(rollup@2.79.2) + "@rollup/plugin-terser": 0.4.4(rollup@2.79.2) + "@surma/rollup-plugin-off-main-thread": 2.2.3 + ajv: 8.18.0 + common-tags: 1.8.2 + fast-json-stable-stringify: 2.1.0 + fs-extra: 9.1.0 + glob: 11.1.0 + lodash: 4.17.23 + pretty-bytes: 5.6.0 + rollup: 2.79.2 + source-map: 0.8.0-beta.0 + stringify-object: 3.3.0 + strip-comments: 2.0.1 + tempy: 0.6.0 + upath: 1.2.0 + workbox-background-sync: 7.4.0 + workbox-broadcast-update: 7.4.0 + workbox-cacheable-response: 7.4.0 + workbox-core: 7.4.0 + workbox-expiration: 7.4.0 + workbox-google-analytics: 7.4.0 + workbox-navigation-preload: 7.4.0 + workbox-precaching: 7.4.0 + workbox-range-requests: 7.4.0 + workbox-recipes: 7.4.0 + workbox-routing: 7.4.0 + workbox-strategies: 7.4.0 + workbox-streams: 7.4.0 + workbox-sw: 7.4.0 + workbox-window: 7.4.0 + transitivePeerDependencies: + - "@types/babel__core" + - supports-color + + workbox-cacheable-response@7.4.0: + dependencies: + workbox-core: 7.4.0 + + workbox-core@7.4.0: {} + + workbox-expiration@7.4.0: + dependencies: + idb: 7.1.1 + workbox-core: 7.4.0 + + workbox-google-analytics@7.4.0: + dependencies: + workbox-background-sync: 7.4.0 + workbox-core: 7.4.0 + workbox-routing: 7.4.0 + workbox-strategies: 7.4.0 + + workbox-navigation-preload@7.4.0: + dependencies: + workbox-core: 7.4.0 + + workbox-precaching@7.4.0: + dependencies: + workbox-core: 7.4.0 + workbox-routing: 7.4.0 + workbox-strategies: 7.4.0 + + workbox-range-requests@7.4.0: + dependencies: + workbox-core: 7.4.0 + + workbox-recipes@7.4.0: + dependencies: + workbox-cacheable-response: 7.4.0 + workbox-core: 7.4.0 + workbox-expiration: 7.4.0 + workbox-precaching: 7.4.0 + workbox-routing: 7.4.0 + workbox-strategies: 7.4.0 + + workbox-routing@7.4.0: + dependencies: + workbox-core: 7.4.0 + + workbox-strategies@7.4.0: + dependencies: + workbox-core: 7.4.0 + + workbox-streams@7.4.0: + dependencies: + workbox-core: 7.4.0 + workbox-routing: 7.4.0 + + workbox-sw@7.4.0: {} + + workbox-window@7.4.0: + dependencies: + "@types/trusted-types": 2.0.7 + workbox-core: 7.4.0 + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 + + wrappy@1.0.2: {} + + write-file-atomic@7.0.0: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.1 + + xml-formatter@3.6.7: + dependencies: + xml-parser-xo: 4.1.5 + + xml-parser-xo@4.1.5: {} + + xmldoc@2.0.3: + dependencies: + sax: 1.4.4 + + xtend@4.0.2: {} + + y18n@4.0.3: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yaml@2.8.2: {} + + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs-parser@20.2.9: {} + + yargs-parser@21.1.1: {} + + yargs-parser@22.0.0: {} + + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yargs@18.0.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 7.2.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + + yocto-queue@0.1.0: {} + + yoctocolors@2.1.2: {} + + zod@4.3.6: {} diff --git a/postcss.config.js b/postcss.config.cjs similarity index 82% rename from postcss.config.js rename to postcss.config.cjs index 3af612e6..ec20059e 100644 --- a/postcss.config.js +++ b/postcss.config.cjs @@ -3,10 +3,9 @@ module.exports = { plugins: [ require("postcss-reporter"), - require("tailwindcss/nesting")(require("postcss-nesting")), require("tailwindcss"), require("postcss-preset-env")({ - stage: 1, + stage: 4, features: { "nesting-rules": false }, }), ...(process.env.NODE_ENV === "production" diff --git a/preload.php b/preload.php index 5840d804..5e9937cf 100644 --- a/preload.php +++ b/preload.php @@ -1,5 +1,10 @@ > Paths to preload. + * @var array Paths to preload. */ private array $paths = [ [ - 'include' => __DIR__ . '/vendor/codeigniter4/framework/system', + 'include' => __DIR__ . '/vendor/codeigniter4/framework/system', // Change this path if using manual installation 'exclude' => [ // Not needed if you don't use them. '/system/Database/OCI8/', '/system/Database/Postgre/', + '/system/Database/SQLite3/', '/system/Database/SQLSRV/', - // Not needed. + // Not needed for web apps. '/system/Database/Seeder.php', '/system/Test/', - '/system/Language/', '/system/CLI/', '/system/Commands/', '/system/Publisher/', '/system/ComposerScripts.php', + // Not Class/Function files. + '/system/Config/Routes.php', + '/system/Language/', + '/system/bootstrap.php', + '/system/util_bootstrap.php', + '/system/rewrite.php', '/Views/', // Errors occur. - '/system/Config/Routes.php', '/system/ThirdParty/', ], ], @@ -75,12 +73,6 @@ class preload $this->loadAutoloader(); } - private function loadAutoloader(): void - { - $paths = new Config\Paths(); - require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php'; - } - /** * Load PHP files. */ @@ -88,12 +80,8 @@ class preload { foreach ($this->paths as $path) { $directory = new RecursiveDirectoryIterator($path['include']); - $fullTree = new RecursiveIteratorIterator($directory); - $phpFiles = new RegexIterator( - $fullTree, - '/.+((? $file) { foreach ($path['exclude'] as $exclude) { @@ -103,10 +91,20 @@ class preload } require_once $file[0]; - echo 'Loaded: ' . $file[0] . "\n"; + // Uncomment only for debugging (to inspect which files are included). + // Never use this in production - preload scripts must not generate output. + // echo 'Loaded: ' . $file[0] . "\n"; } } } + + private function loadAutoloader(): void + { + $paths = new Paths(); + require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'Boot.php'; + + Boot::preload($paths); + } } (new preload())->load(); diff --git a/public/.htaccess b/public/.htaccess index 9fe97382..396272bc 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -1,5 +1,5 @@ # Disable directory browsing -Options All -Indexes +Options -Indexes # ---------------------------------------------------------------------- # Rewrite engine @@ -45,5 +45,5 @@ Options All -Indexes # Disable server signature start - ServerSignature Off +ServerSignature Off # Disable server signature end diff --git a/public/index.php b/public/index.php index cc758a8b..070db6ca 100644 --- a/public/index.php +++ b/public/index.php @@ -2,15 +2,42 @@ declare(strict_types=1); -use CodeIgniter\Config\DotEnv; +use CodeIgniter\Boot; use Config\Paths; -use Config\Services; + +/* + *--------------------------------------------------------------- + * CHECK PHP VERSION + *--------------------------------------------------------------- + */ + +$minPhpVersion = '8.5'; // If you update this, don't forget to update `spark`. +if (version_compare(PHP_VERSION, $minPhpVersion, '<')) { + $message = sprintf( + 'Your PHP version must be %s or higher to run CodeIgniter. Current version: %s', + $minPhpVersion, + PHP_VERSION, + ); + + header('HTTP/1.1 503 Service Unavailable.', true, 503); + echo $message; + + exit(1); +} + +/* + *--------------------------------------------------------------- + * SET THE CURRENT DIRECTORY + *--------------------------------------------------------------- + */ // Path to the front controller (this file) define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR); // Ensure the current directory is pointing to the front controller's directory -chdir(FCPATH); +if (getcwd() . DIRECTORY_SEPARATOR !== FCPATH) { + chdir(FCPATH); +} /* *--------------------------------------------------------------- @@ -21,41 +48,14 @@ chdir(FCPATH); * and fires up an environment-specific bootstrapping. */ -// Load our paths config file +// LOAD OUR PATHS CONFIG FILE // This is the line that might need to be changed, depending on your folder structure. require FCPATH . '../app/Config/Paths.php'; // ^^^ Change this line if you move your application folder $paths = new Paths(); -// Location of the framework bootstrap file. -require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php'; +// LOAD THE FRAMEWORK BOOTSTRAP FILE +require $paths->systemDirectory . '/Boot.php'; -// Load environment settings from .env files into $_SERVER and $_ENV -require_once SYSTEMPATH . 'Config/DotEnv.php'; -(new DotEnv(ROOTPATH))->load(); - -/* - * --------------------------------------------------------------- - * GRAB OUR CODEIGNITER INSTANCE - * --------------------------------------------------------------- - * - * The CodeIgniter class contains the core functionality to make - * the application run, and does all of the dirty work to get - * the pieces all working together. - */ - -$app = Services::codeigniter(); -$app->initialize(); -$context = is_cli() ? 'php-cli' : 'web'; -$app->setContext($context); - -/* - *--------------------------------------------------------------- - * LAUNCH THE APPLICATION - *--------------------------------------------------------------- - * Now that everything is setup, it's time to actually fire - * up the engines and make this app do its thang. - */ - -$app->run(); +exit(Boot::bootWeb($paths)); diff --git a/public/media/castopod-avatar_medium.webp b/public/media/castopod-avatar_medium.webp deleted file mode 100644 index 910a2b35..00000000 Binary files a/public/media/castopod-avatar_medium.webp and /dev/null differ diff --git a/public/media/castopod-avatar_thumbnail.webp b/public/media/castopod-avatar_thumbnail.webp deleted file mode 100644 index b0625dc0..00000000 Binary files a/public/media/castopod-avatar_thumbnail.webp and /dev/null differ diff --git a/public/media/castopod-avatar_tiny.webp b/public/media/castopod-avatar_tiny.webp deleted file mode 100644 index 21cc75d8..00000000 Binary files a/public/media/castopod-avatar_tiny.webp and /dev/null differ diff --git a/public/media/castopod-banner-amber_federation.jpg b/public/media/castopod-banner-amber_federation.jpg deleted file mode 100644 index 425305f3..00000000 Binary files a/public/media/castopod-banner-amber_federation.jpg and /dev/null differ diff --git a/public/media/castopod-banner-amber_medium.webp b/public/media/castopod-banner-amber_medium.webp deleted file mode 100644 index 234ec532..00000000 Binary files a/public/media/castopod-banner-amber_medium.webp and /dev/null differ diff --git a/public/media/castopod-banner-amber_small.webp b/public/media/castopod-banner-amber_small.webp deleted file mode 100644 index 8c9ec454..00000000 Binary files a/public/media/castopod-banner-amber_small.webp and /dev/null differ diff --git a/public/media/castopod-banner-crimson_federation.jpg b/public/media/castopod-banner-crimson_federation.jpg deleted file mode 100644 index a016c399..00000000 Binary files a/public/media/castopod-banner-crimson_federation.jpg and /dev/null differ diff --git a/public/media/castopod-banner-crimson_medium.webp b/public/media/castopod-banner-crimson_medium.webp deleted file mode 100644 index 48589283..00000000 Binary files a/public/media/castopod-banner-crimson_medium.webp and /dev/null differ diff --git a/public/media/castopod-banner-crimson_small.webp b/public/media/castopod-banner-crimson_small.webp deleted file mode 100644 index 1291f73b..00000000 Binary files a/public/media/castopod-banner-crimson_small.webp and /dev/null differ diff --git a/public/media/castopod-banner-jacaranda_federation.jpg b/public/media/castopod-banner-jacaranda_federation.jpg deleted file mode 100644 index 33eada3c..00000000 Binary files a/public/media/castopod-banner-jacaranda_federation.jpg and /dev/null differ diff --git a/public/media/castopod-banner-jacaranda_medium.webp b/public/media/castopod-banner-jacaranda_medium.webp deleted file mode 100644 index ffca50ee..00000000 Binary files a/public/media/castopod-banner-jacaranda_medium.webp and /dev/null differ diff --git a/public/media/castopod-banner-jacaranda_small.webp b/public/media/castopod-banner-jacaranda_small.webp deleted file mode 100644 index b1154d22..00000000 Binary files a/public/media/castopod-banner-jacaranda_small.webp and /dev/null differ diff --git a/public/media/castopod-banner-lake_federation.jpg b/public/media/castopod-banner-lake_federation.jpg deleted file mode 100644 index f7527948..00000000 Binary files a/public/media/castopod-banner-lake_federation.jpg and /dev/null differ diff --git a/public/media/castopod-banner-lake_medium.webp b/public/media/castopod-banner-lake_medium.webp deleted file mode 100644 index 4dcd70df..00000000 Binary files a/public/media/castopod-banner-lake_medium.webp and /dev/null differ diff --git a/public/media/castopod-banner-lake_small.webp b/public/media/castopod-banner-lake_small.webp deleted file mode 100644 index b3e5c539..00000000 Binary files a/public/media/castopod-banner-lake_small.webp and /dev/null differ diff --git a/public/media/castopod-banner-onyx_federation.jpg b/public/media/castopod-banner-onyx_federation.jpg deleted file mode 100644 index 73e4423a..00000000 Binary files a/public/media/castopod-banner-onyx_federation.jpg and /dev/null differ diff --git a/public/media/castopod-banner-onyx_medium.webp b/public/media/castopod-banner-onyx_medium.webp deleted file mode 100644 index 189f77f5..00000000 Binary files a/public/media/castopod-banner-onyx_medium.webp and /dev/null differ diff --git a/public/media/castopod-banner-onyx_small.webp b/public/media/castopod-banner-onyx_small.webp deleted file mode 100644 index 81bd7fff..00000000 Binary files a/public/media/castopod-banner-onyx_small.webp and /dev/null differ diff --git a/public/media/castopod-banner-pine_federation.jpg b/public/media/castopod-banner-pine_federation.jpg deleted file mode 100644 index 9e9df5cb..00000000 Binary files a/public/media/castopod-banner-pine_federation.jpg and /dev/null differ diff --git a/public/media/castopod-banner-pine_medium.webp b/public/media/castopod-banner-pine_medium.webp deleted file mode 100644 index 6371a5e5..00000000 Binary files a/public/media/castopod-banner-pine_medium.webp and /dev/null differ diff --git a/public/media/castopod-banner-pine_small.webp b/public/media/castopod-banner-pine_small.webp deleted file mode 100644 index ac43b616..00000000 Binary files a/public/media/castopod-banner-pine_small.webp and /dev/null differ diff --git a/public/media/index.html b/public/media/index.html index eebf8ecb..cf672743 100644 --- a/public/media/index.html +++ b/public/media/index.html @@ -1,4 +1,4 @@ - + 403 Forbidden diff --git a/public/media/persons/index.html b/public/media/persons/index.html index e69de29b..cf672743 100644 --- a/public/media/persons/index.html +++ b/public/media/persons/index.html @@ -0,0 +1,9 @@ + + + + 403 Forbidden + + +

    Directory access is forbidden.

    + + diff --git a/public/media/podcasts/index.html b/public/media/podcasts/index.html new file mode 100644 index 00000000..cf672743 --- /dev/null +++ b/public/media/podcasts/index.html @@ -0,0 +1,9 @@ + + + + 403 Forbidden + + +

    Directory access is forbidden.

    + + diff --git a/rector.php b/rector.php index 881a5ebd..9fb15216 100644 --- a/rector.php +++ b/rector.php @@ -2,50 +2,32 @@ declare(strict_types=1); -use Rector\CodeQuality\Rector\PropertyFetch\ExplicitMethodCallOverMagicGetSetRector; -use Rector\CodingStyle\Rector\ClassMethod\UnSpreadOperatorRector; +use Rector\CodeQuality\Rector\ClassMethod\ExplicitReturnNullRector; use Rector\CodingStyle\Rector\Encapsed\EncapsedStringsToSprintfRector; -use Rector\CodingStyle\Rector\FuncCall\ConsistentPregDelimiterRector; use Rector\CodingStyle\Rector\Stmt\NewlineAfterStatementRector; -use Rector\CodingStyle\Rector\String_\SymplifyQuoteEscapeRector; +use Rector\CodingStyle\Rector\String_\SimplifyQuoteEscapeRector; use Rector\Config\RectorConfig; -use Rector\Core\ValueObject\PhpVersion; +use Rector\DeadCode\Rector\If_\UnwrapFutureCompatibleIfPhpVersionRector; +use Rector\DeadCode\Rector\Stmt\RemoveUnreachableStatementRector; use Rector\EarlyReturn\Rector\If_\ChangeOrIfContinueToMultiContinueRector; -use Rector\EarlyReturn\Rector\If_\ChangeOrIfReturnToEarlyReturnRector; use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector; use Rector\Php71\Rector\FuncCall\RemoveExtraParametersRector; -use Rector\Set\ValueObject\SetList; +use Rector\ValueObject\PhpVersion; -return static function (RectorConfig $rectorConfig): void { - // get parameters - $rectorConfig->paths([ - __DIR__ . '/app', - __DIR__ . '/modules', - __DIR__ . '/tests', - __DIR__ . '/public', - ]); - - // do you need to include constants, class aliases or custom autoloader? files listed will be executed - $rectorConfig->bootstrapFiles([ - __DIR__ . '/vendor/codeigniter4/framework/system/Test/bootstrap.php', - ]); - - // Define what rule sets will be applied - $rectorConfig->sets([SetList::PHP_80, - SetList::TYPE_DECLARATION, - SetList::TYPE_DECLARATION_STRICT, - SetList::CODE_QUALITY, - SetList::CODING_STYLE, - SetList::EARLY_RETURN, - SetList::DEAD_CODE, - ]); - - // auto import fully qualified class names - $rectorConfig->importNames(); - - $rectorConfig->phpVersion(PhpVersion::PHP_80); - - $rectorConfig->skip([ +return RectorConfig::configure() + ->withPaths([__DIR__ . '/app', __DIR__ . '/modules', __DIR__ . '/tests', __DIR__ . '/public']) + ->withBootstrapFiles([__DIR__ . '/vendor/codeigniter4/framework/system/Test/bootstrap.php']) + ->withPhpVersion(PhpVersion::PHP_85) + ->withPhpSets(php85: true) + ->withPreparedSets( + typeDeclarations: true, + codeQuality: true, + codingStyle: true, + earlyReturn: true, + deadCode: true, + ) + ->withImportNames(true, true, true, true) + ->withSkip([ // .mp3 files were somehow processed by rector, so skip all media files __DIR__ . '/public/media/*', @@ -55,32 +37,23 @@ return static function (RectorConfig $rectorConfig): void { __DIR__ . '/modules/Admin/Language/*/PersonsTaxonomy.php', // skip rules from used sets - ChangeOrIfReturnToEarlyReturnRector::class, ChangeOrIfContinueToMultiContinueRector::class, EncapsedStringsToSprintfRector::class, - UnSpreadOperatorRector::class, - ExplicitMethodCallOverMagicGetSetRector::class, RemoveExtraParametersRector::class, + UnwrapFutureCompatibleIfPhpVersionRector::class, + ExplicitReturnNullRector::class, // skip rule in specific directory StringClassNameToClassConstantRector::class => [ __DIR__ . '/app/Language/*', __DIR__ . '/modules/*/Language/*', ], - SymplifyQuoteEscapeRector::class => [ - __DIR__ . '/app/Language/*', - __DIR__ . '/modules/*/Language/*', + SimplifyQuoteEscapeRector::class => [__DIR__ . '/app/Language/*', __DIR__ . '/modules/*/Language/*'], + + NewlineAfterStatementRector::class => [__DIR__ . '/app/Views'], + + RemoveUnreachableStatementRector::class => [ + __DIR__ . '/modules/Install/Controllers/InstallController.php', ], - - NewlineAfterStatementRector::class => [ - __DIR__ . '/app/Views', - ] - ]); - - // Path to phpstan with extensions, that PHPStan in Rector uses to determine types - $rectorConfig->phpstanConfig(__DIR__ . '/phpstan.neon'); - - $rectorConfig->ruleWithConfiguration(ConsistentPregDelimiterRector::class, [ - ConsistentPregDelimiterRector::DELIMITER => '~', - ]); -}; + ]) + ->withPHPStanConfigs([__DIR__ . '/phpstan.neon', 'vendor/codeigniter/phpstan-codeigniter/extension.neon']); diff --git a/resources/icons/custom/_index.php b/resources/icons/custom/_index.php new file mode 100644 index 00000000..0ef2821e --- /dev/null +++ b/resources/icons/custom/_index.php @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/resources/icons/funding/_index.php b/resources/icons/funding/_index.php new file mode 100644 index 00000000..f939dcca --- /dev/null +++ b/resources/icons/funding/_index.php @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/resources/icons/funding/default.svg b/resources/icons/funding/default.svg new file mode 100644 index 00000000..7dd88909 --- /dev/null +++ b/resources/icons/funding/default.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/funding/donorbox.svg b/resources/icons/funding/donorbox.svg new file mode 100644 index 00000000..a9102a6f --- /dev/null +++ b/resources/icons/funding/donorbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/funding/gofundme.svg b/resources/icons/funding/gofundme.svg new file mode 100755 index 00000000..5cee4579 --- /dev/null +++ b/resources/icons/funding/gofundme.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/funding/helloasso.svg b/resources/icons/funding/helloasso.svg new file mode 100755 index 00000000..381aefef --- /dev/null +++ b/resources/icons/funding/helloasso.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/funding/indiegogo.svg b/resources/icons/funding/indiegogo.svg new file mode 100755 index 00000000..6cad95a0 --- /dev/null +++ b/resources/icons/funding/indiegogo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/funding/kickstarter.svg b/resources/icons/funding/kickstarter.svg new file mode 100755 index 00000000..9be7ebac --- /dev/null +++ b/resources/icons/funding/kickstarter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/funding/kisskissbankbank.svg b/resources/icons/funding/kisskissbankbank.svg new file mode 100755 index 00000000..aafd6f4c --- /dev/null +++ b/resources/icons/funding/kisskissbankbank.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/funding/kofi.svg b/resources/icons/funding/kofi.svg new file mode 100644 index 00000000..0bcbc636 --- /dev/null +++ b/resources/icons/funding/kofi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/funding/liberapay.svg b/resources/icons/funding/liberapay.svg new file mode 100755 index 00000000..79b03686 --- /dev/null +++ b/resources/icons/funding/liberapay.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/funding/patreon.svg b/resources/icons/funding/patreon.svg new file mode 100755 index 00000000..0bf9b7f0 --- /dev/null +++ b/resources/icons/funding/patreon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/funding/paypal.svg b/resources/icons/funding/paypal.svg new file mode 100755 index 00000000..9f3be76a --- /dev/null +++ b/resources/icons/funding/paypal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/funding/tipeee.svg b/resources/icons/funding/tipeee.svg new file mode 100755 index 00000000..0c346332 --- /dev/null +++ b/resources/icons/funding/tipeee.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/funding/ulule.svg b/resources/icons/funding/ulule.svg new file mode 100755 index 00000000..72f1a65a --- /dev/null +++ b/resources/icons/funding/ulule.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/_index.php b/resources/icons/podcasting/_index.php new file mode 100644 index 00000000..562854ec --- /dev/null +++ b/resources/icons/podcasting/_index.php @@ -0,0 +1,48 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/antennapod.svg b/resources/icons/podcasting/antennapod.svg new file mode 100755 index 00000000..044712e6 --- /dev/null +++ b/resources/icons/podcasting/antennapod.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/anytime.svg b/resources/icons/podcasting/anytime.svg new file mode 100644 index 00000000..14f22a8b --- /dev/null +++ b/resources/icons/podcasting/anytime.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/apple.svg b/resources/icons/podcasting/apple.svg new file mode 100755 index 00000000..74f3efc7 --- /dev/null +++ b/resources/icons/podcasting/apple.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/blubrry.svg b/resources/icons/podcasting/blubrry.svg new file mode 100755 index 00000000..ca2a4da4 --- /dev/null +++ b/resources/icons/podcasting/blubrry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/breaker.svg b/resources/icons/podcasting/breaker.svg new file mode 100755 index 00000000..d4447648 --- /dev/null +++ b/resources/icons/podcasting/breaker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/breez.svg b/resources/icons/podcasting/breez.svg new file mode 100644 index 00000000..905542ac --- /dev/null +++ b/resources/icons/podcasting/breez.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/castamatic.svg b/resources/icons/podcasting/castamatic.svg new file mode 100644 index 00000000..e0914121 --- /dev/null +++ b/resources/icons/podcasting/castamatic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/castbox.svg b/resources/icons/podcasting/castbox.svg new file mode 100755 index 00000000..c9f27fae --- /dev/null +++ b/resources/icons/podcasting/castbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/castopod.svg b/resources/icons/podcasting/castopod.svg new file mode 100755 index 00000000..b0d4374d --- /dev/null +++ b/resources/icons/podcasting/castopod.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/castro.svg b/resources/icons/podcasting/castro.svg new file mode 100755 index 00000000..0e075b05 --- /dev/null +++ b/resources/icons/podcasting/castro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/deezer.svg b/resources/icons/podcasting/deezer.svg new file mode 100755 index 00000000..bcd3b7b3 --- /dev/null +++ b/resources/icons/podcasting/deezer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/default.svg b/resources/icons/podcasting/default.svg new file mode 100644 index 00000000..6437d60d --- /dev/null +++ b/resources/icons/podcasting/default.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/episodes-fm.svg b/resources/icons/podcasting/episodes-fm.svg new file mode 100644 index 00000000..e5ed1e34 --- /dev/null +++ b/resources/icons/podcasting/episodes-fm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/fountain.svg b/resources/icons/podcasting/fountain.svg new file mode 100644 index 00000000..796047e7 --- /dev/null +++ b/resources/icons/podcasting/fountain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/fyyd.svg b/resources/icons/podcasting/fyyd.svg new file mode 100755 index 00000000..696d4983 --- /dev/null +++ b/resources/icons/podcasting/fyyd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/google.svg b/resources/icons/podcasting/google.svg new file mode 100755 index 00000000..ecd0e555 --- /dev/null +++ b/resources/icons/podcasting/google.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/gpodder.svg b/resources/icons/podcasting/gpodder.svg new file mode 100644 index 00000000..b2b4b86d --- /dev/null +++ b/resources/icons/podcasting/gpodder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/ivoox.svg b/resources/icons/podcasting/ivoox.svg new file mode 100755 index 00000000..8cd43cb6 --- /dev/null +++ b/resources/icons/podcasting/ivoox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/listennotes.svg b/resources/icons/podcasting/listennotes.svg new file mode 100755 index 00000000..f5336785 --- /dev/null +++ b/resources/icons/podcasting/listennotes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/overcast.svg b/resources/icons/podcasting/overcast.svg new file mode 100755 index 00000000..0fef49c4 --- /dev/null +++ b/resources/icons/podcasting/overcast.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/playerfm.svg b/resources/icons/podcasting/playerfm.svg new file mode 100755 index 00000000..fe15dc7a --- /dev/null +++ b/resources/icons/podcasting/playerfm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/plink.svg b/resources/icons/podcasting/plink.svg new file mode 100644 index 00000000..f300d7b2 --- /dev/null +++ b/resources/icons/podcasting/plink.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/pocketcasts.svg b/resources/icons/podcasting/pocketcasts.svg new file mode 100755 index 00000000..1815c259 --- /dev/null +++ b/resources/icons/podcasting/pocketcasts.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/podbean.svg b/resources/icons/podcasting/podbean.svg new file mode 100755 index 00000000..dc522606 --- /dev/null +++ b/resources/icons/podcasting/podbean.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/podcastaddict.svg b/resources/icons/podcasting/podcastaddict.svg new file mode 100755 index 00000000..c16a756d --- /dev/null +++ b/resources/icons/podcasting/podcastaddict.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/podcastguru.svg b/resources/icons/podcasting/podcastguru.svg new file mode 100644 index 00000000..ea61e070 --- /dev/null +++ b/resources/icons/podcasting/podcastguru.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/podcastindex.svg b/resources/icons/podcasting/podcastindex.svg new file mode 100755 index 00000000..40659726 --- /dev/null +++ b/resources/icons/podcasting/podcastindex.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/podchaser.svg b/resources/icons/podcasting/podchaser.svg new file mode 100755 index 00000000..f3fd817b --- /dev/null +++ b/resources/icons/podcasting/podchaser.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/podcloud.svg b/resources/icons/podcasting/podcloud.svg new file mode 100755 index 00000000..ab39751b --- /dev/null +++ b/resources/icons/podcasting/podcloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/podfriend.svg b/resources/icons/podcasting/podfriend.svg new file mode 100755 index 00000000..026b66af --- /dev/null +++ b/resources/icons/podcasting/podfriend.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/podinstall.svg b/resources/icons/podcasting/podinstall.svg new file mode 100755 index 00000000..773cef9b --- /dev/null +++ b/resources/icons/podcasting/podinstall.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/podlink.svg b/resources/icons/podcasting/podlink.svg new file mode 100755 index 00000000..44c9abfb --- /dev/null +++ b/resources/icons/podcasting/podlink.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/podlp.svg b/resources/icons/podcasting/podlp.svg new file mode 100644 index 00000000..55fcc6a1 --- /dev/null +++ b/resources/icons/podcasting/podlp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/podnews.svg b/resources/icons/podcasting/podnews.svg new file mode 100644 index 00000000..fd9302c8 --- /dev/null +++ b/resources/icons/podcasting/podnews.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/podtail.svg b/resources/icons/podcasting/podtail.svg new file mode 100755 index 00000000..222d9086 --- /dev/null +++ b/resources/icons/podcasting/podtail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/podverse.svg b/resources/icons/podcasting/podverse.svg new file mode 100755 index 00000000..e823ae45 --- /dev/null +++ b/resources/icons/podcasting/podverse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/radiopublic.svg b/resources/icons/podcasting/radiopublic.svg new file mode 100755 index 00000000..5c6e8893 --- /dev/null +++ b/resources/icons/podcasting/radiopublic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/sphinxchat.svg b/resources/icons/podcasting/sphinxchat.svg new file mode 100644 index 00000000..b02f3575 --- /dev/null +++ b/resources/icons/podcasting/sphinxchat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/spotify.svg b/resources/icons/podcasting/spotify.svg new file mode 100755 index 00000000..2068709c --- /dev/null +++ b/resources/icons/podcasting/spotify.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/spreaker.svg b/resources/icons/podcasting/spreaker.svg new file mode 100755 index 00000000..dcb7573f --- /dev/null +++ b/resources/icons/podcasting/spreaker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/stitcher.svg b/resources/icons/podcasting/stitcher.svg new file mode 100755 index 00000000..50541d55 --- /dev/null +++ b/resources/icons/podcasting/stitcher.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/truefans.svg b/resources/icons/podcasting/truefans.svg new file mode 100644 index 00000000..e8aa9c14 --- /dev/null +++ b/resources/icons/podcasting/truefans.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/tsacdop.svg b/resources/icons/podcasting/tsacdop.svg new file mode 100644 index 00000000..680bda13 --- /dev/null +++ b/resources/icons/podcasting/tsacdop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/tunein.svg b/resources/icons/podcasting/tunein.svg new file mode 100755 index 00000000..2e97932b --- /dev/null +++ b/resources/icons/podcasting/tunein.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/podcasting/youtube-music.svg b/resources/icons/podcasting/youtube-music.svg new file mode 100755 index 00000000..55b6d3dd --- /dev/null +++ b/resources/icons/podcasting/youtube-music.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/_index.php b/resources/icons/social/_index.php new file mode 100644 index 00000000..72ef25ca --- /dev/null +++ b/resources/icons/social/_index.php @@ -0,0 +1,29 @@ + \ No newline at end of file diff --git a/resources/icons/social/castopod.svg b/resources/icons/social/castopod.svg new file mode 100755 index 00000000..b0d4374d --- /dev/null +++ b/resources/icons/social/castopod.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/default.svg b/resources/icons/social/default.svg new file mode 100644 index 00000000..80fa3727 --- /dev/null +++ b/resources/icons/social/default.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/discord.svg b/resources/icons/social/discord.svg new file mode 100644 index 00000000..b58f33e9 --- /dev/null +++ b/resources/icons/social/discord.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/discourse.svg b/resources/icons/social/discourse.svg new file mode 100644 index 00000000..b8f621c7 --- /dev/null +++ b/resources/icons/social/discourse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/facebook.svg b/resources/icons/social/facebook.svg new file mode 100755 index 00000000..5246848b --- /dev/null +++ b/resources/icons/social/facebook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/funkwhale.svg b/resources/icons/social/funkwhale.svg new file mode 100755 index 00000000..8ec1da29 --- /dev/null +++ b/resources/icons/social/funkwhale.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/instagram.svg b/resources/icons/social/instagram.svg new file mode 100755 index 00000000..18f914d8 --- /dev/null +++ b/resources/icons/social/instagram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/linkedin.svg b/resources/icons/social/linkedin.svg new file mode 100755 index 00000000..06cc775d --- /dev/null +++ b/resources/icons/social/linkedin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/mastodon.svg b/resources/icons/social/mastodon.svg new file mode 100755 index 00000000..ea82e727 --- /dev/null +++ b/resources/icons/social/mastodon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/matrix.svg b/resources/icons/social/matrix.svg new file mode 100644 index 00000000..ae72a3bd --- /dev/null +++ b/resources/icons/social/matrix.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/misskey.svg b/resources/icons/social/misskey.svg new file mode 100644 index 00000000..bd256f34 --- /dev/null +++ b/resources/icons/social/misskey.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/mobilizon.svg b/resources/icons/social/mobilizon.svg new file mode 100755 index 00000000..55166c3c --- /dev/null +++ b/resources/icons/social/mobilizon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/peertube.svg b/resources/icons/social/peertube.svg new file mode 100755 index 00000000..07e78d42 --- /dev/null +++ b/resources/icons/social/peertube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/pixelfed.svg b/resources/icons/social/pixelfed.svg new file mode 100755 index 00000000..70d7f222 --- /dev/null +++ b/resources/icons/social/pixelfed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/pleroma.svg b/resources/icons/social/pleroma.svg new file mode 100644 index 00000000..06795c62 --- /dev/null +++ b/resources/icons/social/pleroma.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/plume.svg b/resources/icons/social/plume.svg new file mode 100755 index 00000000..17a3e787 --- /dev/null +++ b/resources/icons/social/plume.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/reddit.svg b/resources/icons/social/reddit.svg new file mode 100755 index 00000000..c6f879fe --- /dev/null +++ b/resources/icons/social/reddit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/slack.svg b/resources/icons/social/slack.svg new file mode 100755 index 00000000..4bddffc1 --- /dev/null +++ b/resources/icons/social/slack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/telegram.svg b/resources/icons/social/telegram.svg new file mode 100644 index 00000000..8c1bb674 --- /dev/null +++ b/resources/icons/social/telegram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/threads.svg b/resources/icons/social/threads.svg new file mode 100644 index 00000000..691d7276 --- /dev/null +++ b/resources/icons/social/threads.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/tiktok.svg b/resources/icons/social/tiktok.svg new file mode 100755 index 00000000..c68b6645 --- /dev/null +++ b/resources/icons/social/tiktok.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/twitch.svg b/resources/icons/social/twitch.svg new file mode 100755 index 00000000..14747c46 --- /dev/null +++ b/resources/icons/social/twitch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/twitter.svg b/resources/icons/social/twitter.svg new file mode 100755 index 00000000..1d17015b --- /dev/null +++ b/resources/icons/social/twitter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/writefreely.svg b/resources/icons/social/writefreely.svg new file mode 100755 index 00000000..05965a88 --- /dev/null +++ b/resources/icons/social/writefreely.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/x.svg b/resources/icons/social/x.svg new file mode 100755 index 00000000..9da09775 --- /dev/null +++ b/resources/icons/social/x.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/social/youtube.svg b/resources/icons/social/youtube.svg new file mode 100755 index 00000000..279e6b32 --- /dev/null +++ b/resources/icons/social/youtube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/Resources/js/modules/Charts.ts b/resources/js/_modules/Charts.ts similarity index 99% rename from app/Resources/js/modules/Charts.ts rename to resources/js/_modules/Charts.ts index bac6f232..c949599b 100644 --- a/app/Resources/js/modules/Charts.ts +++ b/resources/js/_modules/Charts.ts @@ -149,7 +149,7 @@ const drawXYDurationChart = ( const yAxis = chart.yAxes.push(new am4charts.DurationAxis()); yAxis.baseUnit = "second"; - chart.durationFormatter.durationFormat = "hh'h,' mm'mn'"; + chart.durationFormatter.durationFormat = "hh'h'mm"; // Add data chart.dataSource.url = dataUrl || ""; diff --git a/app/Resources/js/modules/ClientTimezone.ts b/resources/js/_modules/ClientTimezone.ts similarity index 100% rename from app/Resources/js/modules/ClientTimezone.ts rename to resources/js/_modules/ClientTimezone.ts diff --git a/app/Resources/js/modules/Clipboard.ts b/resources/js/_modules/Clipboard.ts similarity index 95% rename from app/Resources/js/modules/Clipboard.ts rename to resources/js/_modules/Clipboard.ts index 7a344a86..defb176a 100644 --- a/app/Resources/js/modules/Clipboard.ts +++ b/resources/js/_modules/Clipboard.ts @@ -10,7 +10,6 @@ const Clipboard = (): void => { ); if (element) { button.addEventListener("click", () => { - console.log(element); element.select(); element.setSelectionRange(0, element.value.length); document.execCommand("copy"); diff --git a/app/Resources/js/modules/DateTimePicker.ts b/resources/js/_modules/DateTimePicker.ts similarity index 100% rename from app/Resources/js/modules/DateTimePicker.ts rename to resources/js/_modules/DateTimePicker.ts diff --git a/app/Resources/js/modules/Dropdown.ts b/resources/js/_modules/Dropdown.ts similarity index 100% rename from app/Resources/js/modules/Dropdown.ts rename to resources/js/_modules/Dropdown.ts diff --git a/app/Resources/js/modules/EpisodesMap.ts b/resources/js/_modules/EpisodesMap.ts similarity index 91% rename from app/Resources/js/modules/EpisodesMap.ts rename to resources/js/_modules/EpisodesMap.ts index 390564f9..bc0b2ed5 100644 --- a/app/Resources/js/modules/EpisodesMap.ts +++ b/resources/js/_modules/EpisodesMap.ts @@ -11,14 +11,11 @@ import { MarkerClusterGroup } from "leaflet.markercluster"; import "leaflet.markercluster/dist/MarkerCluster.css"; import "leaflet.markercluster/dist/MarkerCluster.Default.css"; import "leaflet/dist/leaflet.css"; -import markerIconRetina from "../../images/marker/marker-icon-2x.png"; -import markerIcon from "../../images/marker/marker-icon.png"; -import markerShadow from "../../images/marker/marker-shadow.png"; Marker.prototype.options.icon = icon({ - iconRetinaUrl: markerIconRetina, - iconUrl: markerIcon, - shadowUrl: markerShadow, + iconRetinaUrl: "/assets/images/marker/marker-icon-2x.png", + iconUrl: "/assets/images/marker/marker-icon.png", + shadowUrl: "/assets/images/marker/marker-shadow.png", iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], diff --git a/resources/js/_modules/FieldArray.ts b/resources/js/_modules/FieldArray.ts new file mode 100644 index 00000000..05baad4e --- /dev/null +++ b/resources/js/_modules/FieldArray.ts @@ -0,0 +1,159 @@ +import Tooltip from "./Tooltip"; + +const FieldArray = (): void => { + const fieldArrays: NodeListOf = + document.querySelectorAll("[data-field-array]"); + + for (let i = 0; i < fieldArrays.length; i++) { + const fieldArray = fieldArrays[i]; + const fieldArrayContainer = fieldArray.querySelector( + "[data-field-array-container]" + ); + const items: NodeListOf = fieldArray.querySelectorAll( + "[data-field-array-item]" + ); + const addButton = fieldArray.querySelector( + "button[data-field-array-add]" + ) as HTMLButtonElement; + + const deleteButtons: NodeListOf = + fieldArray.querySelectorAll("[data-field-array-delete]"); + + deleteButtons.forEach((deleteBtn) => { + deleteBtn.addEventListener("click", (e) => { + e.preventDefault(); + deleteBtn.blur(); + fieldArrayContainer + ?.querySelector( + `[data-field-array-item="${deleteBtn.dataset.fieldArrayDelete}"]` + ) + ?.remove(); + }); + }); + + // create base element to clone + const baseItem = items[0].cloneNode(true) as HTMLElement; + + const elements: NodeListOf = baseItem.querySelectorAll( + "input, select, textarea" + ); + + elements.forEach((element) => { + element.value = ""; + }); + + if (fieldArrayContainer && addButton) { + addButton.addEventListener("click", (event) => { + event.preventDefault(); + + const newItem = baseItem.cloneNode(true) as HTMLElement; + + const deleteBtn: HTMLButtonElement | null = newItem.querySelector( + "button[data-field-array-delete]" + ); + + if (deleteBtn) { + deleteBtn.addEventListener("click", () => { + deleteBtn.blur(); + newItem.remove(); + }); + + fieldArrayContainer.appendChild(newItem); + newItem.scrollIntoView({ + behavior: "auto", + block: "center", + inline: "center", + }); + + // reload tooltip module for showing remove button label + Tooltip(); + + // focus to first form element if mouse click + if (event.screenX !== 0 && event.screenY !== 0) { + const elements: NodeListOf = + newItem.querySelectorAll("input, select, textarea"); + + if (elements.length > 0) { + elements[0].focus(); + } + } + } + }); + + const updateIndexes = () => { + // get last child item to set item count + const items: NodeListOf = + fieldArrayContainer.querySelectorAll("[data-field-array-item]"); + + let itemIndex = 0; + items.forEach((item) => { + const itemNumber: HTMLElement | null = item.querySelector( + "[data-field-array-number]" + ); + + if (itemNumber) { + itemNumber.innerHTML = "#"; + const indexNum = itemIndex + 1; + if (item.dataset.fieldArrayItem !== itemIndex.toString()) { + item.classList.add("motion-safe:animate-single-pulse"); + setTimeout(() => { + item.classList.remove("motion-safe:animate-single-pulse"); + itemNumber.innerHTML = indexNum.toString(); + }, 300); + } else { + itemNumber.innerHTML = indexNum.toString(); + } + } + + item.dataset.fieldArrayItem = itemIndex.toString(); + const deleteBtn = item.querySelector( + "button[data-field-array-delete]" + ) as HTMLButtonElement | null; + + if (deleteBtn) { + deleteBtn.dataset.fieldArrayDelete = itemIndex.toString(); + } + + const itemElements: NodeListOf = + item.querySelectorAll("input, select, textarea"); + + itemElements.forEach((element) => { + const label: HTMLLabelElement | null = item.querySelector( + `label[for="${element.id}"]` + ); + + const elementID = element.name.replace( + /(.*\[)\d+?(\].*)/g, + `$1${itemIndex}$2` + ); + + if (label) { + label.htmlFor = elementID; + } + + element.id = elementID; + element.name = elementID; + }); + + itemIndex++; + }); + }; + + // add mutation observer to run index updates when field array + // items are added or removed + const callback = function (mutationList: MutationRecord[]) { + for (const mutation of mutationList) { + if (mutation.type === "childList") { + updateIndexes(); + } + } + }; + + const observer = new MutationObserver(callback); + + observer.observe(fieldArrayContainer, { childList: true }); + } + } +}; + +export default FieldArray; diff --git a/app/Resources/js/modules/HotKeys.ts b/resources/js/_modules/HotKeys.ts similarity index 100% rename from app/Resources/js/modules/HotKeys.ts rename to resources/js/_modules/HotKeys.ts diff --git a/app/Resources/js/modules/Modal.ts b/resources/js/_modules/Modal.ts similarity index 100% rename from app/Resources/js/modules/Modal.ts rename to resources/js/_modules/Modal.ts diff --git a/app/Resources/js/modules/PublishMessageWarning.ts b/resources/js/_modules/PublishMessageWarning.ts similarity index 100% rename from app/Resources/js/modules/PublishMessageWarning.ts rename to resources/js/_modules/PublishMessageWarning.ts diff --git a/app/Resources/js/modules/Select.ts b/resources/js/_modules/Select.ts similarity index 90% rename from app/Resources/js/modules/Select.ts rename to resources/js/_modules/Select.ts index d3c5dce4..840dfed8 100644 --- a/app/Resources/js/modules/Select.ts +++ b/resources/js/_modules/Select.ts @@ -10,38 +10,42 @@ const Select = (): void => { const select = selects[i]; new Choices(select, { + allowHTML: false, loadingText: select.dataset.loadingText, itemSelectText: select.dataset.selectText, maxItemText: select.dataset.maxItemText, noChoicesText: select.dataset.noChoicesText, noResultsText: select.dataset.noResultsText, classNames: { - containerOuter: "choices", + activeState: "is-active", + addChoice: ["choices__item--selectable", "add-choice"], + button: "choices__button", containerInner: "choices__inner", - input: "choices__input", - inputCloned: "choices__input--cloned", - list: "choices__list", - listItems: "choices__list--multiple", - listSingle: "choices__list--single", - listDropdown: "choices__list--dropdown", - item: "choices__item", - itemSelectable: "choices__item--selectable", - itemDisabled: "choices__item--disabled", - itemChoice: "choices__item--choice", - placeholder: "choices__placeholder", + containerOuter: "choices", + description: "choices__description", + disabledState: "is-disabled", + flippedState: "is-flipped", + focusState: "is-focused", group: "choices__group", groupHeading: "choices__heading", - button: "choices__button", - activeState: "is-active", - focusState: "is-focused", - openState: "is-open", - disabledState: "is-disabled", highlightedState: "is-highlighted", - selectedState: "is-selected", - flippedState: "is-flipped", + input: "choices__input", + inputCloned: "choices__input--cloned", + item: "choices__item", + itemChoice: "choices__item--choice", + itemDisabled: "choices__item--disabled", + itemSelectable: "choices__item--selectable", + list: "choices__list", + listDropdown: "choices__list--dropdown", + listItems: "choices__list--multiple", + listSingle: "choices__list--single", loadingState: "is-loading", - noResults: "has-no-results", noChoices: "has-no-choices", + noResults: "has-no-results", + notice: "choices__notice", + openState: "is-open", + placeholder: "choices__placeholder", + selectedState: "is-selected", }, }); } diff --git a/app/Resources/js/modules/MultiSelect.ts b/resources/js/_modules/SelectMulti.ts similarity index 88% rename from app/Resources/js/modules/MultiSelect.ts rename to resources/js/_modules/SelectMulti.ts index 34a12592..064bc78a 100644 --- a/app/Resources/js/modules/MultiSelect.ts +++ b/resources/js/_modules/SelectMulti.ts @@ -1,6 +1,6 @@ import Choices from "choices.js"; -const MultiSelect = (): void => { +const SelectMulti = (): void => { // Pass single element const multiSelects: NodeListOf = document.querySelectorAll("select[multiple]"); @@ -9,6 +9,7 @@ const MultiSelect = (): void => { const multiSelect = multiSelects[i]; new Choices(multiSelect, { + allowHTML: false, maxItemCount: parseInt(multiSelect.dataset.maxItemCount || "-1"), loadingText: multiSelect.dataset.loadingText, itemSelectText: multiSelect.dataset.selectText, @@ -17,35 +18,38 @@ const MultiSelect = (): void => { noResultsText: multiSelect.dataset.noResultsText, removeItemButton: true, classNames: { - containerOuter: "choices", + activeState: "is-active", + addChoice: ["choices__item--selectable", "add-choice"], + button: "choices__button", containerInner: "choices__inner", - input: "choices__input", - inputCloned: "choices__input--cloned", - list: "choices__list", - listItems: "choices__list--multiple", - listSingle: "choices__list--single", - listDropdown: "choices__list--dropdown", - item: "choices__item", - itemSelectable: "choices__item--selectable", - itemDisabled: "choices__item--disabled", - itemChoice: "choices__item--choice", - placeholder: "choices__placeholder", + containerOuter: "choices", + description: "choices__description", + disabledState: "is-disabled", + flippedState: "is-flipped", + focusState: "is-focused", group: "choices__group", groupHeading: "choices__heading", - button: "choices__button", - activeState: "is-active", - focusState: "is-focused", - openState: "is-open", - disabledState: "is-disabled", highlightedState: "is-highlighted", - selectedState: "is-selected", - flippedState: "is-flipped", + input: "choices__input", + inputCloned: "choices__input--cloned", + item: "choices__item", + itemChoice: "choices__item--choice", + itemDisabled: "choices__item--disabled", + itemSelectable: "choices__item--selectable", + list: "choices__list", + listDropdown: "choices__list--dropdown", + listItems: "choices__list--multiple", + listSingle: "choices__list--single", loadingState: "is-loading", - noResults: "has-no-results", noChoices: "has-no-choices", + noResults: "has-no-results", + notice: "choices__notice", + openState: "is-open", + placeholder: "choices__placeholder", + selectedState: "is-selected", }, }); } }; -export default MultiSelect; +export default SelectMulti; diff --git a/app/Resources/js/modules/SidebarToggler.ts b/resources/js/_modules/SidebarToggler.ts similarity index 100% rename from app/Resources/js/modules/SidebarToggler.ts rename to resources/js/_modules/SidebarToggler.ts diff --git a/app/Resources/js/modules/Slugify.ts b/resources/js/_modules/Slugify.ts similarity index 100% rename from app/Resources/js/modules/Slugify.ts rename to resources/js/_modules/Slugify.ts diff --git a/app/Resources/js/modules/ThemePicker.ts b/resources/js/_modules/ThemePicker.ts similarity index 82% rename from app/Resources/js/modules/ThemePicker.ts rename to resources/js/_modules/ThemePicker.ts index d302e463..850fb0a6 100644 --- a/app/Resources/js/modules/ThemePicker.ts +++ b/resources/js/_modules/ThemePicker.ts @@ -14,7 +14,7 @@ const ThemePicker = (): void => { const url: string | undefined = button.dataset.url; if (url) { button.addEventListener("click", () => { - iframeTextArea.value = ``; + iframeTextArea.value = ``; urlTextArea.value = url; iframe.src = url; }); diff --git a/app/Resources/js/modules/Time.ts b/resources/js/_modules/Time.ts similarity index 100% rename from app/Resources/js/modules/Time.ts rename to resources/js/_modules/Time.ts diff --git a/app/Resources/js/modules/Toggler.ts b/resources/js/_modules/Toggler.ts similarity index 100% rename from app/Resources/js/modules/Toggler.ts rename to resources/js/_modules/Toggler.ts diff --git a/app/Resources/js/modules/Tooltip.ts b/resources/js/_modules/Tooltip.ts similarity index 98% rename from app/Resources/js/modules/Tooltip.ts rename to resources/js/_modules/Tooltip.ts index 0d0d2733..5ea75754 100644 --- a/app/Resources/js/modules/Tooltip.ts +++ b/resources/js/_modules/Tooltip.ts @@ -1,7 +1,7 @@ -import { Coords } from "@floating-ui/core"; import { arrow, computePosition, + Coords, flip, offset, Placement, diff --git a/app/Resources/js/modules/ValidateFileSize.ts b/resources/js/_modules/ValidateFileSize.ts similarity index 100% rename from app/Resources/js/modules/ValidateFileSize.ts rename to resources/js/_modules/ValidateFileSize.ts diff --git a/app/Resources/js/modules/VideoClipBuilder.ts b/resources/js/_modules/VideoClipBuilder.ts similarity index 100% rename from app/Resources/js/modules/VideoClipBuilder.ts rename to resources/js/_modules/VideoClipBuilder.ts diff --git a/app/Resources/js/modules/audio-clipper.ts b/resources/js/_modules/audio-clipper.ts similarity index 81% rename from app/Resources/js/modules/audio-clipper.ts rename to resources/js/_modules/audio-clipper.ts index 18df16e0..a5521273 100644 --- a/app/Resources/js/modules/audio-clipper.ts +++ b/resources/js/_modules/audio-clipper.ts @@ -4,10 +4,10 @@ import { property, query, queryAll, - queryAssignedNodes, + queryAssignedElements, state, } from "lit/decorators.js"; -import WaveSurfer from "wavesurfer.js"; +import WaveSurfer, { WaveSurferOptions } from "wavesurfer.js"; enum ActionType { StretchLeft, @@ -17,7 +17,9 @@ enum ActionType { interface Action { type: ActionType; - payload?: any; + payload?: { + offset: number; + }; } interface EventElement { @@ -27,14 +29,14 @@ interface EventElement { @customElement("audio-clipper") export class AudioClipper extends LitElement { - @queryAssignedNodes("audio", true) - _audio!: NodeListOf; + @queryAssignedElements({ slot: "audio", flatten: true }) + _audio!: Array; - @queryAssignedNodes("start_time", true) - _startTimeInput!: NodeListOf; + @queryAssignedElements({ slot: "start_time", flatten: true }) + _startTimeInput!: Array; - @queryAssignedNodes("duration", true) - _durationInput!: NodeListOf; + @queryAssignedElements({ slot: "duration", flatten: true }) + _durationInput!: Array; @query(".slider") _sliderNode!: HTMLDivElement; @@ -45,9 +47,12 @@ export class AudioClipper extends LitElement { @query(".slider__segment-content") _segmentContentNode!: HTMLDivElement; - @query(".slider__segment-progress-handle") + @query(".slider__segment-progress-handle--main") _progressNode!: HTMLDivElement; + @query(".slider__segment-progress-handle--ghost") + _progressGhostNode!: HTMLDivElement; + @query(".slider__seeking-placeholder") _seekingNode!: HTMLDivElement; @@ -60,6 +65,9 @@ export class AudioClipper extends LitElement { @queryAll(".slider__segment-handle") _segmentHandleNodes!: NodeListOf; + @property({ type: Number, attribute: "audio-duration" }) + audioDuration = 0; + @property({ type: Number, attribute: "start-time" }) initStartTime = 0; @@ -81,6 +89,9 @@ export class AudioClipper extends LitElement { @property({ attribute: "trim-end-label" }) trimEndLabel = "Trim end"; + @state() + _canInteract = false; + @state() _isPlaying = false; @@ -93,9 +104,6 @@ export class AudioClipper extends LitElement { @state() _action: Action | null = null; - @state() - _audioDuration = 0; - @state() _sliderWidth = 0; @@ -116,7 +124,15 @@ export class AudioClipper extends LitElement { _windowEvents: EventElement[] = [ { - events: ["load", "resize"], + events: ["load"], + onEvent: () => { + this._canInteract = true; + this._sliderWidth = this._sliderNode.clientWidth; + this.setSegmentPosition(); + }, + }, + { + events: ["resize"], onEvent: () => { this._sliderWidth = this._sliderNode.clientWidth; this.setSegmentPosition(); @@ -130,9 +146,12 @@ export class AudioClipper extends LitElement { onEvent: () => { if (this._action !== null) { document.body.style.cursor = ""; - if (this._action.type === ActionType.Seek && this._seekingTime) { + if ( + this._action.type === ActionType.Seek && + this._seekingTime !== null + ) { this._audio[0].currentTime = this._seekingTime; - this._seekingTime = 0; + this._seekingTime = null; } this._action = null; } @@ -141,14 +160,24 @@ export class AudioClipper extends LitElement { { events: ["mousemove"], onEvent: (event: Event) => { - if (this._action !== null) { - this.updatePosition(event as MouseEvent); - } + this.updatePosition(event as MouseEvent); }, }, ]; _audioEvents: EventElement[] = [ + { + events: ["loadedmetadata"], + onEvent: () => { + this.audioDuration = this._audio[0].duration; + }, + }, + { + events: ["waiting"], + onEvent: () => { + this._isBuffering = true; + }, + }, { events: ["play"], onEvent: () => { @@ -176,7 +205,7 @@ export class AudioClipper extends LitElement { ); context.fillStyle = "#04AC64"; - const inc = this._bufferingBarNode.width / this._audio[0].duration; + const inc = this._bufferingBarNode.width / this.audioDuration; for (let i = 0; i < this._audio[0].buffered.length; i++) { const startX = this._audio[0].buffered.start(i) * inc; @@ -192,13 +221,11 @@ export class AudioClipper extends LitElement { { events: ["timeupdate"], onEvent: () => { - // TODO: change this? - this._currentTime = parseFloat(this._audio[0].currentTime.toFixed(3)); + this._currentTime = this._audio[0].currentTime; if (this._currentTime > this._clip.endTime) { this.pause(); this._audio[0].currentTime = this._clip.endTime; } else if (this._currentTime < this._clip.startTime) { - this._isBuffering = true; this._audio[0].currentTime = this._clip.startTime; } else { this._isBuffering = false; @@ -233,6 +260,21 @@ export class AudioClipper extends LitElement { }, ]; + _sliderSegmentEvents: EventElement[] = [ + { + events: ["hover"], + onEvent: (event: Event) => { + const ghostHandle = (event.target as HTMLDivElement).querySelector( + ".segment" + ) as HTMLDivElement; + if (ghostHandle) { + ghostHandle.style.opacity = "1"; + ghostHandle.style.transform = "translateX(50)"; + } + }, + }, + ]; + connectedCallback(): void { super.connectedCallback(); @@ -244,7 +286,9 @@ export class AudioClipper extends LitElement { } protected firstUpdated(): void { - this._audioDuration = this._audio[0].duration; + this._sliderWidth = this._sliderNode.clientWidth; + this.setSegmentPosition(); + this._audio[0].volume = this._volume; this._startTimeInput[0].hidden = true; this._durationInput[0].hidden = true; @@ -255,11 +299,10 @@ export class AudioClipper extends LitElement { interact: false, barWidth: 2, barHeight: 1, - // barGap: 4, responsive: true, waveColor: "hsl(0 5% 85%)", cursorColor: "transparent", - }); + } as WaveSurferOptions); this._wavesurfer.load(this._audio[0].src); this.addEventListeners(); @@ -338,11 +381,11 @@ export class AudioClipper extends LitElement { } private getPositionFromSeconds(seconds: number) { - return (seconds * this._sliderWidth) / this._audioDuration; + return (seconds * this._sliderWidth) / this.audioDuration; } private getSecondsFromPosition(position: number) { - return (this._audioDuration * position) / this._sliderWidth; + return (this.audioDuration * position) / this._sliderWidth; } protected updated( @@ -405,14 +448,14 @@ export class AudioClipper extends LitElement { } case ActionType.StretchRight: { let endTime; - if (seconds < this._audioDuration) { + if (seconds < this.audioDuration) { if (seconds < this._clip.startTime + this.minDuration) { endTime = this._clip.startTime + this.minDuration; } else { endTime = seconds; } } else { - endTime = this._audioDuration; + endTime = this.audioDuration; } this._clip = { @@ -459,6 +502,7 @@ export class AudioClipper extends LitElement { const seekingTimePercentage = (seekingTimeSegmentPosition / this._segmentContentNode.clientWidth) * this._segmentContentNode.clientWidth; + this._progressNode.style.transform = `translateX(${seekingTimeSegmentPosition}px)`; this._seekingNode.style.transform = `scaleX(${seekingTimePercentage})`; } @@ -586,6 +630,10 @@ export class AudioClipper extends LitElement { border-top: 10px solid #3b82f6; } + .slider__segment-progress-handle--ghost { + opacity: 0.5; + } + .slider__segment .slider__segment-handle { position: absolute; width: 1rem; @@ -637,7 +685,9 @@ export class AudioClipper extends LitElement { padding: 0.5rem 0.5rem 0.25rem 0.5rem; justify-content: space-between; background-color: hsl(var(--color-background-elevated)); - box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + box-shadow: + 0 1px 3px 0 rgb(0 0 0 / 0.1), + 0 1px 2px -1px rgb(0 0 0 / 0.1); border-radius: 0 0 0.75rem 0.75rem; flex-wrap: wrap; gap: 0.5rem; @@ -670,7 +720,9 @@ export class AudioClipper extends LitElement { border-radius: 9999px; border: none; padding: 0.25rem 0.5rem; - box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + box-shadow: + 0 1px 3px 0 rgb(0 0 0 / 0.1), + 0 1px 2px -1px rgb(0 0 0 / 0.1); } .toolbar button:hover { @@ -684,11 +736,16 @@ export class AudioClipper extends LitElement { var(--tw-ring-offset-width) var(--tw-ring-offset-color); --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), + box-shadow: + var(--tw-ring-offset-shadow), + var(--tw-ring-shadow), 0 0 rgba(0, 0, 0, 0); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), + box-shadow: + var(--tw-ring-offset-shadow), + var(--tw-ring-shadow), 0 0 rgba(0, 0, 0, 0); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), + box-shadow: + var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 rgba(0, 0, 0, 0)); --tw-ring-offset-width: 2px; --tw-ring-opacity: 1; @@ -698,10 +755,27 @@ export class AudioClipper extends LitElement { .toolbar__trim-controls button { font-weight: 600; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, - Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif, BlinkMacSystemFont, - "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, - "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", + font-family: + Inter, + ui-sans-serif, + system-ui, + -apple-system, + Segoe UI, + Roboto, + Ubuntu, + Cantarell, + Noto Sans, + sans-serif, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + "Helvetica Neue", + Arial, + "Noto Sans", + sans-serif, + "Apple Color Emoji", + "Segoe UI Emoji", + "Segoe UI Symbol", "Noto Color Emoji"; } @@ -742,14 +816,19 @@ export class AudioClipper extends LitElement { -
    +
    +
    - ${this._isBuffering + ${this._isBuffering || !this._canInteract ? html` ` : this._isPlaying - ? html` - + ? html` + + + + + ` + : html` - - - ` - : html` - - - `} + + `}
    ; + + @property() + lang = "html"; + + @state() + editorState!: EditorState; + + @state() + editorView!: EditorView; + + _textareaEvents = [ + { + events: ["focus", "invalid"], + onEvent: (_: Event, editor: EditorView) => { + // focus editor when textarea is focused or invalid + editor.focus(); + }, + }, + ]; + + firstUpdated(): void { + const minHeightEditor = EditorView.baseTheme({ + ".cm-content, .cm-gutter": { + minHeight: this._textarea[0].clientHeight + "px", + }, + }); + + const extensions: Extension[] = [ + basicSetup, + keymap.of([indentWithTab]), + minHeightEditor, + syntaxHighlighting(defaultHighlightStyle), + EditorView.updateListener.of((viewUpdate: ViewUpdate) => { + if (viewUpdate.docChanged) { + // Document changed, minify and update textarea value + switch (this.lang) { + case "xml": + this._textarea[0].value = minifyXML( + viewUpdate.state.doc.toString() + ); + break; + case "html": + this._textarea[0].value = minifyHTML( + viewUpdate.state.doc.toString() + ); + break; + default: + this._textarea[0].value = viewUpdate.state.doc.toString(); + break; + } + } + }), + ]; + + let editorContents = ""; + switch (this.lang) { + case "xml": + editorContents = formatXML(this._textarea[0].value); + extensions.push(language.of(xml())); + break; + case "html": + editorContents = prettifyHTML(this._textarea[0].value); + extensions.push(language.of(htmlLang())); + break; + default: + break; + } + + this.editorState = EditorState.create({ + doc: editorContents, + extensions: extensions, + }); + + this.editorView = new EditorView({ + state: this.editorState, + root: this.shadowRoot as ShadowRoot, + parent: this.shadowRoot as ShadowRoot, + }); + + // hide textarea + this._textarea[0].style.position = "absolute"; + this._textarea[0].style.opacity = "0"; + this._textarea[0].style.zIndex = "-9999"; + this._textarea[0].style.pointerEvents = "none"; + + for (const event of this._textareaEvents) { + event.events.forEach((name) => { + this._textarea[0].addEventListener(name, (e) => + event.onEvent(e, this.editorView) + ); + }); + } + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + + for (const event of this._textareaEvents) { + event.events.forEach((name) => { + this._textarea[0].removeEventListener(name, (e) => + event.onEvent(e, this.editorView) + ); + }); + } + } + + static styles = css` + .cm-editor { + border-radius: 0.5rem; + overflow: hidden; + border: 3px solid hsl(var(--color-border-contrast)); + background-color: hsl(var(--color-background-elevated)); + transition-property: + color, + background-color, + border-color, + text-decoration-color, + fill, + stroke, + opacity, + box-shadow, + transform, + filter, + backdrop-filter, + -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + } + .cm-editor.cm-focused { + outline: 2px solid transparent; + box-shadow: + 0 0 0 2px hsl(var(--color-background-elevated)), + 0 0 0 calc(4px) hsl(var(--color-accent-base)); + } + .cm-gutters { + background-color: hsl(var(--color-background-elevated)) !important; + } + + .cm-activeLine { + background-color: hsla( + var(--color-background-highlight) / 0.25 + ) !important; + } + + .cm-activeLineGutter { + background-color: hsl(var(--color-background-highlight)) !important; + } + + .ͼ4 .cm-line { + caret-color: hsl(var(--color-text-base)) !important; + } + + .ͼ1 .cm-cursor { + border: none; + } + `; + + render(): TemplateResult<1> { + return html``; + } +} + +function formatXML(contents: string) { + if (contents === "") { + return contents; + } + + try { + return xmlFormat(contents, { + indentation: " ", + }); + } catch { + // xml doesn't have a root node + const editorContents = xmlFormat("" + contents + "", { + indentation: " ", + }); + // remove root, unnecessary lines and indents + return editorContents + .replace(/^/, "") + .replace(/<\/root>$/, "") + .replace(/^\s*[\r\n]/gm, "") + .replace(/[\r\n] {2}/gm, "\r\n") + .trim(); + } +} + +function minifyXML(contents: string) { + if (contents === "") { + return contents; + } + + try { + return xmlFormat.minify(contents, { + collapseContent: true, + }); + } catch { + const minifiedContent = xmlFormat.minify(`${contents}`, { + collapseContent: true, + }); + // remove root + return minifiedContent.replace(/^/, "").replace(/<\/root>$/, ""); + } +} diff --git a/app/Resources/js/modules/markdown-preview.ts b/resources/js/_modules/markdown-preview.ts similarity index 86% rename from app/Resources/js/modules/markdown-preview.ts rename to resources/js/_modules/markdown-preview.ts index 58733f7b..e2a37612 100644 --- a/app/Resources/js/modules/markdown-preview.ts +++ b/resources/js/_modules/markdown-preview.ts @@ -9,10 +9,10 @@ export class MarkdownPreview extends LitElement { @property() for!: string; - @state() + @property({ attribute: false }) _textarea!: HTMLTextAreaElement; - @state() + @property({ attribute: false }) _markdownToolbar!: MarkdownToolbarElement; @state() @@ -42,11 +42,13 @@ export class MarkdownPreview extends LitElement { markdownToHtml(): string { const renderer = new marked.Renderer(); renderer.link = function () { - // eslint-disable-next-line prefer-rest-params + // eslint-disable-next-line prefer-rest-params, @typescript-eslint/no-explicit-any const link = marked.Renderer.prototype.link.apply(this, arguments as any); return link.replace("; + @queryAssignedElements({ slot: "write", flatten: true }) + _write!: Array; - @queryAssignedNodes("preview", true) - _preview!: NodeListOf; + @queryAssignedElements({ slot: "preview", flatten: true }) + _preview!: Array; connectedCallback(): void { super.connectedCallback(); diff --git a/app/Resources/js/modules/permalink-edit.ts b/resources/js/_modules/permalink-edit.ts similarity index 90% rename from app/Resources/js/modules/permalink-edit.ts rename to resources/js/_modules/permalink-edit.ts index fdda43cf..c0ea0224 100644 --- a/app/Resources/js/modules/permalink-edit.ts +++ b/resources/js/_modules/permalink-edit.ts @@ -1,23 +1,24 @@ import "@github/clipboard-copy-element"; +import ClipboardCopyElement from "@github/clipboard-copy-element"; import { css, html, LitElement, TemplateResult } from "lit"; import { customElement, property, query, - queryAssignedNodes, + queryAssignedElements, state, } from "lit/decorators.js"; @customElement("permalink-edit") export class PermalinkEdit extends LitElement { - @queryAssignedNodes("domain", true) + @queryAssignedElements({ slot: "domain", flatten: true }) _domain!: NodeListOf; - @queryAssignedNodes("slug-input", true) + @queryAssignedElements({ slot: "slug-input", flatten: true }) _slugInput!: NodeListOf; @query("clipboard-copy") - _clipboardCopy!: any; + _clipboardCopy!: ClipboardCopyElement; @property({ attribute: "edit-label" }) editLabel = "Edit"; @@ -25,6 +26,9 @@ export class PermalinkEdit extends LitElement { @property({ attribute: "copy-label" }) copyLabel = "Copy"; + @property({ attribute: "permalink-base" }) + permalinkBase = ""; + @state() isEditable = false; @@ -68,6 +72,10 @@ export class PermalinkEdit extends LitElement { } firstUpdated(): void { + console.log(this._slugInput); + + this.permalinkBase += this.permalinkBase.endsWith("/") ? "" : "/"; + // set permalink value this.setPermalink(); @@ -130,7 +138,7 @@ export class PermalinkEdit extends LitElement { } setPermalink(): void { - this.permalink = this._domain[0].innerHTML + this._slugInput[0].value; + this.permalink = this.permalinkBase + this._slugInput[0].value; } static styles = css` @@ -139,7 +147,7 @@ export class PermalinkEdit extends LitElement { border-color: transparent !important; padding-left: 0 !important; margin-left: -0.25rem !important; - font-weight: 600; + font-weight: 600 !important; } ::slotted([slot="domain"]) { diff --git a/app/Resources/js/modules/play-episode-button.ts b/resources/js/_modules/play-episode-button.ts similarity index 97% rename from app/Resources/js/modules/play-episode-button.ts rename to resources/js/_modules/play-episode-button.ts index 3eee7af5..101868ad 100644 --- a/app/Resources/js/modules/play-episode-button.ts +++ b/resources/js/_modules/play-episode-button.ts @@ -27,15 +27,15 @@ export class PlayEpisodeButton extends LitElement { @property() playingLabel!: string; - @property() - isPlaying!: boolean; - - @property() + @property({ attribute: false }) _castopodAudioPlayer!: HTMLDivElement; - @property() + @property({ attribute: false }) _audio!: HTMLAudioElement; + @state() + isPlaying!: boolean; + @state() _playbackSpeed = 1; @@ -206,7 +206,8 @@ export class PlayEpisodeButton extends LitElement { button:focus { outline: none; - box-shadow: 0 0 0 2px hsl(var(--color-background-base)), + box-shadow: + 0 0 0 2px hsl(var(--color-background-base)), 0 0 0 4px hsl(var(--color-accent-base)); } diff --git a/app/Resources/js/modules/play-soundbite.ts b/resources/js/_modules/play-soundbite.ts similarity index 80% rename from app/Resources/js/modules/play-soundbite.ts rename to resources/js/_modules/play-soundbite.ts index 26f24086..098a73a9 100644 --- a/app/Resources/js/modules/play-soundbite.ts +++ b/resources/js/_modules/play-soundbite.ts @@ -44,11 +44,6 @@ export class PlaySoundbite extends LitElement { name: "timeupdate", onEvent: () => { if (this._audio) { - console.log( - this._audio.currentTime, - this.startTime, - this.startTime + this.duration - ); if (this._audio.currentTime < this.startTime) { this._isLoading = true; this._audio.currentTime = this.startTime; @@ -109,7 +104,8 @@ export class PlaySoundbite extends LitElement { button:focus { outline: none; - box-shadow: 0 0 0 2px hsl(var(--color-background-base)), + box-shadow: + 0 0 0 2px hsl(var(--color-background-base)), 0 0 0 4px hsl(var(--color-accent-base)); } @@ -168,31 +164,31 @@ export class PlaySoundbite extends LitElement { > ` : this._isPlaying - ? html` - + ? html` + + + + + ` + : html` - - ` - : html` - - - `} + `} `; } } diff --git a/app/Resources/js/modules/video-clip-previewer.ts b/resources/js/_modules/video-clip-previewer.ts similarity index 94% rename from app/Resources/js/modules/video-clip-previewer.ts rename to resources/js/_modules/video-clip-previewer.ts index a28c2f2a..34e68335 100644 --- a/app/Resources/js/modules/video-clip-previewer.ts +++ b/resources/js/_modules/video-clip-previewer.ts @@ -2,8 +2,7 @@ import { css, html, LitElement, TemplateResult } from "lit"; import { customElement, property, - queryAssignedNodes, - state, + queryAssignedElements, } from "lit/decorators.js"; import { styleMap } from "lit/directives/style-map.js"; @@ -21,7 +20,7 @@ const formatMap = { @customElement("video-clip-previewer") export class VideoClipPreviewer extends LitElement { - @queryAssignedNodes("preview_image", true) + @queryAssignedElements({ slot: "preview_image", flatten: true }) _image!: NodeListOf; @property() @@ -36,7 +35,7 @@ export class VideoClipPreviewer extends LitElement { @property({ type: Number }) duration!: number; - @state() + @property({ attribute: false }) _previewImage!: HTMLImageElement; protected firstUpdated(): void { @@ -111,7 +110,8 @@ export class VideoClipPreviewer extends LitElement { ::slotted(img) { border-radius: 0.5rem; - box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), + box-shadow: + 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); } `; diff --git a/app/Resources/js/admin-audio-player.ts b/resources/js/admin-audio-player.ts similarity index 91% rename from app/Resources/js/admin-audio-player.ts rename to resources/js/admin-audio-player.ts index 28460b89..7fa61b86 100644 --- a/app/Resources/js/admin-audio-player.ts +++ b/resources/js/admin-audio-player.ts @@ -34,14 +34,14 @@ import { } from "@vime/core"; import "@vime/core/themes/default.css"; import "@vime/core/themes/light.css"; -import "./modules/play-episode-button"; +import "./_modules/play-episode-button"; -// Register Castopod's icons library +// Register Castopod's vm player icons library const library: HTMLVmIconLibraryElement | null = document.querySelector( - 'vm-icon-library[name="castopod-icons"]' + 'vm-icon-library[name="castopod-vm-player-icons"]' ); if (library) { - library.resolver = (iconName) => `/assets/icons/${iconName}.svg`; + library.resolver = (iconName) => `/assets/vm-player-icons/${iconName}.svg`; } // Vime elements for audio player diff --git a/resources/js/admin.ts b/resources/js/admin.ts new file mode 100644 index 00000000..bc87479d --- /dev/null +++ b/resources/js/admin.ts @@ -0,0 +1,43 @@ +import "@github/markdown-toolbar-element"; +import "@github/relative-time-element"; +import "./_modules/audio-clipper"; +import ClientTimezone from "./_modules/ClientTimezone"; +import Clipboard from "./_modules/Clipboard"; +import DateTimePicker from "./_modules/DateTimePicker"; +import Dropdown from "./_modules/Dropdown"; +import HotKeys from "./_modules/HotKeys"; +import "./_modules/markdown-preview"; +import "./_modules/markdown-write-preview"; +import SelectMulti from "./_modules/SelectMulti"; +import "./_modules/permalink-edit"; +import "./_modules/play-soundbite"; +import PublishMessageWarning from "./_modules/PublishMessageWarning"; +import Select from "./_modules/Select"; +import SidebarToggler from "./_modules/SidebarToggler"; +import Slugify from "./_modules/Slugify"; +import ThemePicker from "./_modules/ThemePicker"; +import Time from "./_modules/Time"; +import Tooltip from "./_modules/Tooltip"; +import ValidateFileSize from "./_modules/ValidateFileSize"; +import "./_modules/video-clip-previewer"; +import VideoClipBuilder from "./_modules/VideoClipBuilder"; +import "./_modules/code-editor"; +import "@patternfly/elements/pf-tabs/pf-tabs.js"; +import FieldArray from "./_modules/FieldArray"; + +Dropdown(); +Tooltip(); +Select(); +SelectMulti(); +Slugify(); +SidebarToggler(); +ClientTimezone(); +DateTimePicker(); +Time(); +Clipboard(); +ThemePicker(); +PublishMessageWarning(); +HotKeys(); +ValidateFileSize(); +VideoClipBuilder(); +FieldArray(); diff --git a/resources/js/app.ts b/resources/js/app.ts new file mode 100644 index 00000000..65bf64d4 --- /dev/null +++ b/resources/js/app.ts @@ -0,0 +1,5 @@ +import Dropdown from "./_modules/Dropdown"; +import Tooltip from "./_modules/Tooltip"; + +Dropdown(); +Tooltip(); diff --git a/app/Resources/js/audio-player.ts b/resources/js/audio-player.ts similarity index 92% rename from app/Resources/js/audio-player.ts rename to resources/js/audio-player.ts index 74cebcc2..7c15480f 100644 --- a/app/Resources/js/audio-player.ts +++ b/resources/js/audio-player.ts @@ -35,7 +35,7 @@ import { import "@vime/core/themes/default.css"; import "@vime/core/themes/light.css"; import { html, render } from "lit"; -import "./modules/play-episode-button"; +import "./_modules/play-episode-button"; const player = html`
    - + @@ -86,12 +86,12 @@ const player = html`
    `/assets/icons/${iconName}.svg`; + library.resolver = (iconName) => `/assets/vm-player-icons/${iconName}.svg`; } // Vime elements for audio player diff --git a/resources/js/charts.ts b/resources/js/charts.ts new file mode 100644 index 00000000..9b09ccaa --- /dev/null +++ b/resources/js/charts.ts @@ -0,0 +1,3 @@ +import DrawCharts from "./_modules/Charts"; + +DrawCharts(); diff --git a/app/Resources/js/embed.ts b/resources/js/embed.ts similarity index 93% rename from app/Resources/js/embed.ts rename to resources/js/embed.ts index 9bad2538..d587cb7b 100644 --- a/app/Resources/js/embed.ts +++ b/resources/js/embed.ts @@ -35,12 +35,12 @@ import { import "@vime/core/themes/default.css"; import "@vime/core/themes/light.css"; -// Register Castopod's icons library +// Register Castopod's vm player icons library const library: HTMLVmIconLibraryElement | null = document.querySelector( - 'vm-icon-library[name="castopod-icons"]' + 'vm-icon-library[name="castopod-vm-player-icons"]' ); if (library) { - library.resolver = (iconName) => `/assets/icons/${iconName}.svg`; + library.resolver = (iconName) => `/assets/vm-player-icons/${iconName}.svg`; } // Vime elements for audio player diff --git a/app/Resources/js/error.ts b/resources/js/error.ts similarity index 100% rename from app/Resources/js/error.ts rename to resources/js/error.ts diff --git a/resources/js/install.ts b/resources/js/install.ts new file mode 100644 index 00000000..5f57a192 --- /dev/null +++ b/resources/js/install.ts @@ -0,0 +1,3 @@ +import Tooltip from "./_modules/Tooltip"; + +Tooltip(); diff --git a/resources/js/map.ts b/resources/js/map.ts new file mode 100644 index 00000000..2f3785e7 --- /dev/null +++ b/resources/js/map.ts @@ -0,0 +1,3 @@ +import DrawEpisodesMaps from "./_modules/EpisodesMap"; + +DrawEpisodesMaps(); diff --git a/resources/js/podcast.ts b/resources/js/podcast.ts new file mode 100644 index 00000000..f16c5618 --- /dev/null +++ b/resources/js/podcast.ts @@ -0,0 +1,10 @@ +import "@github/relative-time-element"; +import SidebarToggler from "./_modules/SidebarToggler"; +import Time from "./_modules/Time"; +import Toggler from "./_modules/Toggler"; +import Tooltip from "./_modules/Tooltip"; + +Time(); +Toggler(); +Tooltip(); +SidebarToggler(); diff --git a/app/Resources/js/typings.d.ts b/resources/js/typings.d.ts similarity index 100% rename from app/Resources/js/typings.d.ts rename to resources/js/typings.d.ts diff --git a/app/Resources/js/vite-env.d.ts b/resources/js/vite-env.d.ts similarity index 100% rename from app/Resources/js/vite-env.d.ts rename to resources/js/vite-env.d.ts diff --git a/app/Resources/fonts/inter-600.woff2 b/resources/static/fonts/inter-600.woff2 similarity index 100% rename from app/Resources/fonts/inter-600.woff2 rename to resources/static/fonts/inter-600.woff2 diff --git a/app/Resources/fonts/inter-regular.woff2 b/resources/static/fonts/inter-regular.woff2 similarity index 100% rename from app/Resources/fonts/inter-regular.woff2 rename to resources/static/fonts/inter-regular.woff2 diff --git a/app/Resources/fonts/kumbh-sans-700.woff2 b/resources/static/fonts/kumbh-sans-700.woff2 similarity index 100% rename from app/Resources/fonts/kumbh-sans-700.woff2 rename to resources/static/fonts/kumbh-sans-700.woff2 diff --git a/app/Resources/fonts/kumbh-sans-regular.woff2 b/resources/static/fonts/kumbh-sans-regular.woff2 similarity index 100% rename from app/Resources/fonts/kumbh-sans-regular.woff2 rename to resources/static/fonts/kumbh-sans-regular.woff2 diff --git a/app/Resources/fonts/noto-sans-mono-regular.woff2 b/resources/static/fonts/noto-sans-mono-regular.woff2 similarity index 100% rename from app/Resources/fonts/noto-sans-mono-regular.woff2 rename to resources/static/fonts/noto-sans-mono-regular.woff2 diff --git a/public/media/castopod-avatar.jpg b/resources/static/images/castopod-avatar.jpg similarity index 100% rename from public/media/castopod-avatar.jpg rename to resources/static/images/castopod-avatar.jpg diff --git a/public/media/castopod-banner-amber.jpg b/resources/static/images/castopod-banner-amber.jpg similarity index 100% rename from public/media/castopod-banner-amber.jpg rename to resources/static/images/castopod-banner-amber.jpg diff --git a/public/media/castopod-banner-crimson.jpg b/resources/static/images/castopod-banner-crimson.jpg similarity index 100% rename from public/media/castopod-banner-crimson.jpg rename to resources/static/images/castopod-banner-crimson.jpg diff --git a/public/media/castopod-banner-jacaranda.jpg b/resources/static/images/castopod-banner-jacaranda.jpg similarity index 100% rename from public/media/castopod-banner-jacaranda.jpg rename to resources/static/images/castopod-banner-jacaranda.jpg diff --git a/public/media/castopod-banner-lake.jpg b/resources/static/images/castopod-banner-lake.jpg similarity index 100% rename from public/media/castopod-banner-lake.jpg rename to resources/static/images/castopod-banner-lake.jpg diff --git a/public/media/castopod-banner-onyx.jpg b/resources/static/images/castopod-banner-onyx.jpg similarity index 100% rename from public/media/castopod-banner-onyx.jpg rename to resources/static/images/castopod-banner-onyx.jpg diff --git a/public/media/castopod-banner-pine.jpg b/resources/static/images/castopod-banner-pine.jpg similarity index 100% rename from public/media/castopod-banner-pine.jpg rename to resources/static/images/castopod-banner-pine.jpg diff --git a/resources/static/images/castopod-logo-base.svg b/resources/static/images/castopod-logo-base.svg new file mode 100644 index 00000000..72221cfa --- /dev/null +++ b/resources/static/images/castopod-logo-base.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/static/images/castopod-logo.svg b/resources/static/images/castopod-logo.svg new file mode 100644 index 00000000..55f4b22b --- /dev/null +++ b/resources/static/images/castopod-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/static/images/castopod-mascot_confused.svg b/resources/static/images/castopod-mascot_confused.svg new file mode 100644 index 00000000..89d51627 --- /dev/null +++ b/resources/static/images/castopod-mascot_confused.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/Resources/images/marker/marker-icon-2x.png b/resources/static/images/marker/marker-icon-2x.png similarity index 100% rename from app/Resources/images/marker/marker-icon-2x.png rename to resources/static/images/marker/marker-icon-2x.png diff --git a/app/Resources/images/marker/marker-icon.png b/resources/static/images/marker/marker-icon.png similarity index 100% rename from app/Resources/images/marker/marker-icon.png rename to resources/static/images/marker/marker-icon.png diff --git a/app/Resources/images/marker/marker-shadow.png b/resources/static/images/marker/marker-shadow.png similarity index 100% rename from app/Resources/images/marker/marker-shadow.png rename to resources/static/images/marker/marker-shadow.png diff --git a/resources/static/vm-player-icons/check.svg b/resources/static/vm-player-icons/check.svg new file mode 100644 index 00000000..bf36197d --- /dev/null +++ b/resources/static/vm-player-icons/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/static/vm-player-icons/pause.svg b/resources/static/vm-player-icons/pause.svg new file mode 100644 index 00000000..ea2847b9 --- /dev/null +++ b/resources/static/vm-player-icons/pause.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/static/vm-player-icons/play.svg b/resources/static/vm-player-icons/play.svg new file mode 100644 index 00000000..75ad8b10 --- /dev/null +++ b/resources/static/vm-player-icons/play.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/static/vm-player-icons/settings.svg b/resources/static/vm-player-icons/settings.svg new file mode 100644 index 00000000..a8cd5895 --- /dev/null +++ b/resources/static/vm-player-icons/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/static/vm-player-icons/volume-high.svg b/resources/static/vm-player-icons/volume-high.svg new file mode 100644 index 00000000..f352c50a --- /dev/null +++ b/resources/static/vm-player-icons/volume-high.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/static/vm-player-icons/volume-low.svg b/resources/static/vm-player-icons/volume-low.svg new file mode 100644 index 00000000..999e6126 --- /dev/null +++ b/resources/static/vm-player-icons/volume-low.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/static/vm-player-icons/volume-mute.svg b/resources/static/vm-player-icons/volume-mute.svg new file mode 100644 index 00000000..44f6c50c --- /dev/null +++ b/resources/static/vm-player-icons/volume-mute.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/Resources/styles/breadcrumb.css b/resources/styles/_modules/breadcrumb.css similarity index 89% rename from app/Resources/styles/breadcrumb.css rename to resources/styles/_modules/breadcrumb.css index f0c31905..b1ef8a30 100644 --- a/app/Resources/styles/breadcrumb.css +++ b/resources/styles/_modules/breadcrumb.css @@ -4,6 +4,7 @@ .breadcrumb-item + .breadcrumb-item::before { @apply inline-block px-1; + color: hsl(var(--color-text-muted)); content: "/"; } @@ -14,10 +15,6 @@ &:hover { @apply underline; } - - &:focus { - @apply ring-accent; - } } .breadcrumb-item.active { diff --git a/app/Resources/styles/choices.css b/resources/styles/_modules/choices.css similarity index 83% rename from app/Resources/styles/choices.css rename to resources/styles/_modules/choices.css index 9e50896b..a83a52f1 100644 --- a/app/Resources/styles/choices.css +++ b/resources/styles/_modules/choices.css @@ -1,6 +1,6 @@ -/*=============================== +/* =============================== = Choices = -===============================*/ +=============================== */ @layer components { .choices { @@ -9,10 +9,6 @@ font-size: 16px; } - .choices:focus { - outline: none; - } - .choices:last-child { margin-bottom: 0; } @@ -21,8 +17,6 @@ .choices.is-disabled .choices__input { opacity: 0.5; cursor: not-allowed; - -webkit-user-select: none; - -ms-user-select: none; user-select: none; } @@ -31,17 +25,16 @@ } .choices [hidden] { - display: none !important; + position: absolute; + opacity: 0; + z-index: -9999; + pointer-events: none; } .choices[data-type*="select-one"] { cursor: pointer; } - .choices[data-type*="select-one"] .choices__inner { - padding-bottom: 7.5px; - } - .choices[data-type*="select-one"] .choices__input { display: block; width: 100%; @@ -71,7 +64,7 @@ } .choices[data-type*="select-one"] .choices__button:focus { - box-shadow: 0px 0px 0px 2px #00bcd4; + box-shadow: 0 0 0 2px #00bcd4; } .choices[data-type*="select-one"] @@ -80,13 +73,12 @@ display: none; } - .choices[data-type*="select-one"]:after { + .choices[data-type*="select-one"]::after { content: ""; height: 0; width: 0; border-style: solid; - border-color: hsl(var(--color-text-muted)) transparent transparent - transparent; + border-color: hsl(var(--color-text-muted)) transparent transparent; border-width: 5px; position: absolute; right: 11.5px; @@ -95,13 +87,12 @@ pointer-events: none; } - .choices[data-type*="select-one"].is-open:after { - border-color: transparent transparent hsl(var(--color-text-muted)) - transparent; + .choices[data-type*="select-one"].is-open::after { + border-color: transparent transparent hsl(var(--color-text-muted)); margin-top: -7.5px; } - .choices[data-type*="select-one"][dir="rtl"]:after { + .choices[data-type*="select-one"][dir="rtl"]::after { left: 11.5px; right: auto; } @@ -124,7 +115,7 @@ display: inline-block; margin-left: 8px; padding-left: 16px; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath d='M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z'/%3E%3C/svg%3E"); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2300574B'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath d='M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z'/%3E%3C/svg%3E"); background-size: 16px; width: 8px; line-height: 1; @@ -140,7 +131,7 @@ } .choices__inner { - @apply p-2 rounded-lg border-contrast bg-elevated border-3; + @apply px-3 py-2 rounded-lg border-contrast bg-elevated border-3 transition; box-shadow: 2px 2px 0 hsl(var(--color-border-contrast)); display: inline-block; @@ -151,13 +142,9 @@ overflow: hidden; } - .choices[data-type*="select-multiple"] .choices__inner { - @apply pb-1; - } - .is-focused .choices__inner, .is-open .choices__inner { - @apply ring-accent ring-inset; + @apply ring-accent; } .is-open .choices__inner { @@ -188,11 +175,11 @@ } .choices__list--multiple { - display: inline; + @apply inline-flex gap-2 mr-2 items-center; } .choices__list--multiple .choices__item { - @apply inline-block px-2 py-1 mb-1 mr-1 text-sm align-middle rounded text-accent-contrast bg-accent-base; + @apply inline-block font-semibold px-1 py-0.5 text-sm align-middle rounded text-accent-hover bg-base border-accent-base ring-2 ring-accent-base; word-break: break-all; box-sizing: border-box; @@ -208,17 +195,18 @@ } .choices__list--multiple .choices__item.is-highlighted { - @apply bg-accent-base; + @apply bg-subtle; } .is-disabled .choices__list--multiple .choices__item { - background-color: #aaaaaa; + background-color: #aaa; border: 1px solid #919191; } .choices__list--dropdown { @apply z-50 border-2 shadow-lg border-contrast; - visibility: hidden; + + display: none; position: absolute; width: 100%; background-color: hsl(var(--color-background-elevated)); @@ -226,11 +214,10 @@ margin-top: -1px; overflow: hidden; word-break: break-all; - will-change: visibility; } .choices__list--dropdown.is-active { - visibility: visible; + display: block; } .is-open .choices__list--dropdown { @@ -239,6 +226,7 @@ .is-flipped .choices__list--dropdown { @apply border-b-0 rounded-t-lg rounded-b-none border-t-3; + top: auto; bottom: 100%; margin-top: 0; @@ -254,6 +242,8 @@ } .choices__list--dropdown .choices__item { + @apply break-normal; + position: relative; padding: 10px; font-size: 14px; @@ -263,11 +253,12 @@ text-align: right; } - @media (min-width: 640px) { + @media (width >= 640px) { .choices__list--dropdown .choices__item--selectable { padding-right: 100px; } - .choices__list--dropdown .choices__item--selectable:after { + + .choices__list--dropdown .choices__item--selectable::after { content: attr(data-select-text); font-size: 12px; opacity: 0; @@ -276,12 +267,14 @@ top: 50%; transform: translateY(-50%); } + [dir="rtl"] .choices__list--dropdown .choices__item--selectable { text-align: right; padding-left: 100px; padding-right: 10px; } - [dir="rtl"] .choices__list--dropdown .choices__item--selectable:after { + + [dir="rtl"] .choices__list--dropdown .choices__item--selectable::after { right: auto; left: 10px; } @@ -291,7 +284,7 @@ background-color: hsl(var(--color-background-highlight)); } - .choices__list--dropdown .choices__item--selectable.is-highlighted:after { + .choices__list--dropdown .choices__item--selectable.is-highlighted::after { opacity: 0.5; } @@ -305,8 +298,6 @@ .choices__item--disabled { cursor: not-allowed; - -webkit-user-select: none; - -ms-user-select: none; user-select: none; opacity: 0.5; } @@ -321,8 +312,6 @@ .choices__button { text-indent: -9999px; - -webkit-appearance: none; - -moz-appearance: none; appearance: none; border: 0; background-color: transparent; @@ -331,12 +320,8 @@ cursor: pointer; } - .choices__button:focus { - outline: none; - } - .choices__input { - @apply mb-1 align-middle bg-elevated; + @apply align-middle bg-elevated; display: inline-block; font-size: 14px; @@ -349,6 +334,7 @@ .choices__input:focus { @apply outline-none; + box-shadow: none; } @@ -360,6 +346,10 @@ .choices__placeholder { opacity: 0.5; } + + .choices__description { + @apply text-xs block text-skin-muted break-normal; + } } -/*===== End of Choices ======*/ +/* ===== End of Choices ====== */ diff --git a/app/Resources/styles/colorRadioBtn.css b/resources/styles/_modules/colorRadioBtn.css similarity index 99% rename from app/Resources/styles/colorRadioBtn.css rename to resources/styles/_modules/colorRadioBtn.css index 73a39d5a..a6581249 100644 --- a/app/Resources/styles/colorRadioBtn.css +++ b/resources/styles/_modules/colorRadioBtn.css @@ -20,6 +20,7 @@ & + label { @apply inline-block w-16 h-16 text-sm font-semibold rounded-full cursor-pointer border-contrast bg-accent-base text-accent-contrast border-3; + color: hsl(var(--color-text-muted)); } } diff --git a/app/Resources/styles/colors.css b/resources/styles/_modules/colors.css similarity index 93% rename from app/Resources/styles/colors.css rename to resources/styles/_modules/colors.css index 27b5a5b4..d59e79ba 100644 --- a/app/Resources/styles/colors.css +++ b/resources/styles/_modules/colors.css @@ -4,21 +4,18 @@ --color-accent-hover: 172 100% 17%; --color-accent-muted: 131 100% 12%; --color-accent-contrast: 0 0% 100%; - --color-heading-foreground: 172 100% 17%; --color-heading-background: 111 64% 94%; - --color-background-elevated: 0 0% 100%; --color-background-base: 173 44% 96%; --color-background-navigation: 172 100% 17%; + --color-background-navigation-active: 131 100% 12%; --color-background-header: 172 100% 17%; --color-background-highlight: 111 64% 94%; --color-background-backdrop: 0 0% 50%; - --color-border-subtle: 111 42% 86%; --color-border-contrast: 0 0% 0%; --color-border-navigation: 131 100% 12%; - --color-text-base: 158 8% 3%; --color-text-muted: 172 8% 38%; } diff --git a/resources/styles/_modules/custom.css b/resources/styles/_modules/custom.css new file mode 100644 index 00000000..e327b2ca --- /dev/null +++ b/resources/styles/_modules/custom.css @@ -0,0 +1,105 @@ +@layer base { + html { + scroll-behavior: smooth; + } + + .form-helper { + @apply text-skin-muted; + } + + select { + box-shadow: 2px 2px 0 hsl(var(--color-border-contrast)); + } +} + +@layer components { + .post-content { + & a { + @apply text-sm font-semibold text-accent-base hover:text-accent-hover; + } + } + + .ring-accent { + @apply outline-none ring-2 ring-offset-2 ring-accent-base; + + /* FIXME: why doesn't ring-accent-base work? */ + --tw-ring-opacity: 1; + --tw-ring-color: hsl(var(--color-accent-base) / var(--tw-ring-opacity)); + --tw-ring-offset-color: hsl(var(--color-background-base)); + } + + .rounded-conditional-b-xl { + border-bottom-right-radius: max( + 0px, + min(0.75rem, calc((100vw - 0.75rem - 100%) * 9999)) + ); + border-bottom-left-radius: max( + 0px, + min(0.75rem, calc((100vw - 0.75rem - 100%) * 9999)) + ); + } + + .rounded-conditional-2xl { + border-radius: max(0px, min(1rem, calc((100vw - 1rem - 100%) * 9999))); + } + + .rounded-conditional-full { + border-radius: max(0px, min(9999px, calc((100vw - 1rem - 100%) * 9999))); + } + + .backdrop-gradient { + background-image: linear-gradient( + 180deg, + hsl(0deg 0% 35.29% / 0%) 0%, + hsl(0deg 0% 34.53% / 3.44%) 16.36%, + hsl(0deg 0% 32.42% / 12.5%) 33.34%, + hsl(0deg 0% 29.18% / 25.3%) 50.1%, + hsl(0deg 0% 24.96% / 40%) 65.75%, + hsl(0deg 0% 19.85% / 54.7%) 79.43%, + hsl(0deg 0% 13.95% / 67.5%) 90.28%, + hsl(0deg 0% 7.32% / 76.6%) 97.43%, + hsl(0deg 0% 0% / 80%) 100% + ); + } + + .backdrop-gradient-accent { + /* stylelint-disable-next-line declaration-property-value-no-unknown */ + background-image: linear-gradient( + 180deg, + theme(colors.background.base / 0.4) 0%, + theme(colors.background.base / 0.6) 65.75%, + theme(colors.background.base / 1) 90.28%, + theme(colors.background.base / 1) 97.43%, + theme(colors.background.base / 1) 100% + ); + } + + .bg-stripes-default { + background-image: repeating-linear-gradient( + -45deg, + #f3f4f6, + #f3f4f6 10px, + #e5e7eb 10px, + #e5e7eb 20px + ); + } + + .bg-stripes-warning { + background-image: repeating-linear-gradient( + -45deg, + #fde047, + #fde047 10px, + #facc15 10px, + #facc15 20px + ); + } + + .divide-fieldset-y > :not([hidden], legend) ~ :not([hidden], legend) { + @apply pt-4; + + --tw-divide-y-reverse: 0; + + border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(1px * var(--tw-divide-y-reverse)); + } +} diff --git a/app/Resources/styles/dropdown.css b/resources/styles/_modules/dropdown.css similarity index 99% rename from app/Resources/styles/dropdown.css rename to resources/styles/_modules/dropdown.css index 850f8d35..e501d4e4 100644 --- a/app/Resources/styles/dropdown.css +++ b/resources/styles/_modules/dropdown.css @@ -2,6 +2,7 @@ [data-dropdown="menu"] { @apply absolute z-50; } + [data-dropdown="menu"]:not([data-show]) { @apply hidden; } diff --git a/app/Resources/styles/fonts.css b/resources/styles/_modules/fonts.css similarity index 63% rename from app/Resources/styles/fonts.css rename to resources/styles/_modules/fonts.css index cee3974e..f2d9aa00 100644 --- a/app/Resources/styles/fonts.css +++ b/resources/styles/_modules/fonts.css @@ -5,7 +5,7 @@ font-style: normal; font-weight: 400; font-display: swap; - src: url("/fonts/kumbh-sans-regular.woff2") format("woff2"); + src: url("/assets/fonts/kumbh-sans-regular.woff2") format("woff2"); } /* kumbh-sans-700 */ @@ -14,25 +14,25 @@ font-style: normal; font-weight: 700; font-display: swap; - src: url("/fonts/kumbh-sans-700.woff2") format("woff2"); + src: url("/assets/fonts/kumbh-sans-700.woff2") format("woff2"); } /* inter-regular */ @font-face { - font-family: "Inter"; + font-family: Inter; font-style: normal; font-weight: 400; font-display: swap; - src: url("/fonts/inter-regular.woff2") format("woff2"); + src: url("/assets/fonts/inter-regular.woff2") format("woff2"); } /* inter-600 */ @font-face { - font-family: "Inter"; + font-family: Inter; font-style: normal; font-weight: 600; font-display: swap; - src: url("/fonts/inter-600.woff2") format("woff2"); + src: url("/assets/fonts/inter-600.woff2") format("woff2"); } /* noto-sans-mono-regular */ @@ -41,6 +41,6 @@ font-style: normal; font-weight: 400; font-display: swap; - src: url("/fonts/noto-sans-mono-regular.woff2") format("woff2"); + src: url("/assets/fonts/noto-sans-mono-regular.woff2") format("woff2"); } } diff --git a/app/Resources/styles/formInputTabs.css b/resources/styles/_modules/formInputTabs.css similarity index 99% rename from app/Resources/styles/formInputTabs.css rename to resources/styles/_modules/formInputTabs.css index 4e71aa16..4c5f21e5 100644 --- a/app/Resources/styles/formInputTabs.css +++ b/resources/styles/_modules/formInputTabs.css @@ -30,6 +30,7 @@ .form-input-tabs > input:checked + label::after { @apply absolute inset-x-0 bottom-0 w-full mx-auto bg-accent-base; + content: ""; height: 0.2rem; } diff --git a/app/Resources/styles/inputRange.css b/resources/styles/_modules/inputRange.css similarity index 95% rename from app/Resources/styles/inputRange.css rename to resources/styles/_modules/inputRange.css index 053df137..b474963a 100644 --- a/app/Resources/styles/inputRange.css +++ b/resources/styles/_modules/inputRange.css @@ -4,8 +4,11 @@ position: relative; width: 12.5em; height: 5.25em; - font: 1em/1 arial, sans-serif; + font: + 1em/1 arial, + sans-serif; } + [type="range"] { flex: 1; margin: 0; @@ -14,10 +17,12 @@ background: transparent; font: inherit; } + [type="range"], [type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; + appearance: none; } + [type="range"]::-webkit-slider-runnable-track { box-sizing: border-box; border: none; @@ -25,6 +30,7 @@ height: 0.25em; background: #ccc; } + [type="range"]::-moz-range-track { box-sizing: border-box; border: none; @@ -32,6 +38,7 @@ height: 0.25em; background: #ccc; } + [type="range"]::-ms-track { box-sizing: border-box; border: none; @@ -39,6 +46,7 @@ height: 0.25em; background: #ccc; } + [type="range"]::-webkit-slider-thumb { margin-top: -0.625em; box-sizing: border-box; @@ -48,6 +56,7 @@ border-radius: 50%; background: #f90; } + [type="range"]::-moz-range-thumb { box-sizing: border-box; border: none; @@ -56,6 +65,7 @@ border-radius: 50%; background: #f90; } + [type="range"]::-ms-thumb { margin-top: 0; box-sizing: border-box; @@ -65,12 +75,15 @@ border-radius: 50%; background: #f90; } + [type="range"]::-ms-tooltip { display: none; } + [type="range"] ~ output { display: none; } + .js [type="range"] ~ output { display: block; position: absolute; diff --git a/resources/styles/_modules/radioBtn.css b/resources/styles/_modules/radioBtn.css new file mode 100644 index 00000000..e892ee0d --- /dev/null +++ b/resources/styles/_modules/radioBtn.css @@ -0,0 +1,33 @@ +@layer components { + .form-radio-btn { + @apply absolute right-4 top-4 border-contrast border-3 text-accent-base transition; + + &:focus { + @apply ring-accent; + } + + &:checked { + & + label { + @apply text-accent-hover bg-base border-accent-base shadow-none; + } + + & + label .form-radio-btn-description { + @apply text-accent-base; + } + } + + & + label { + @apply h-full w-full inline-flex flex-col items-start py-3 px-4 text-sm font-bold rounded-lg cursor-pointer border-contrast bg-elevated border-3 transition-all; + + box-shadow: 2px 2px 0 hsl(var(--color-border-contrast)); + } + + & + label span { + @apply pr-8; + } + + & + label .form-radio-btn-description { + @apply font-normal text-xs text-skin-muted text-balance; + } + } +} diff --git a/app/Resources/styles/radioToggler.css b/resources/styles/_modules/radioToggler.css similarity index 100% rename from app/Resources/styles/radioToggler.css rename to resources/styles/_modules/radioToggler.css diff --git a/app/Resources/styles/readMore.css b/resources/styles/_modules/readMore.css similarity index 94% rename from app/Resources/styles/readMore.css rename to resources/styles/_modules/readMore.css index d83e0bee..38f40e02 100644 --- a/app/Resources/styles/readMore.css +++ b/resources/styles/_modules/readMore.css @@ -6,12 +6,14 @@ Read more component (basic unstyled component) @layer components { .read-more { @apply flex flex-col items-start; + /* You can update this variable directly in the html with the style attribute: style="--line-clamp: 3" */ --line-clamp: 3; } .read-more__text { @apply overflow-hidden; + display: -webkit-box; -webkit-line-clamp: var(--line-clamp); -webkit-box-orient: vertical; @@ -19,8 +21,8 @@ Read more component (basic unstyled component) .read-more__checkbox { @apply absolute overflow-hidden whitespace-nowrap; - clip: rect(0 0 0 0); - clip-path: inset(100%); + + clip-path: inset(50%); height: 1px; width: 1px; } @@ -31,7 +33,7 @@ Read more component (basic unstyled component) /* Don't forget focus and hover styles for accessibility! */ .read-more__checkbox:focus ~ .read-more__label { - @apply ring; + @apply ring-accent; } .read-more__checkbox:hover ~ .read-more__label { @@ -48,6 +50,7 @@ Read more component (basic unstyled component) .read-more__checkbox:checked ~ .read-more__text { --line-clamp: none; + -webkit-line-clamp: var(--line-clamp); } } diff --git a/app/Resources/styles/seeMore.css b/resources/styles/_modules/seeMore.css similarity index 95% rename from app/Resources/styles/seeMore.css rename to resources/styles/_modules/seeMore.css index 4166b147..6a191f57 100644 --- a/app/Resources/styles/seeMore.css +++ b/resources/styles/_modules/seeMore.css @@ -5,11 +5,13 @@ .see-more__content { @apply relative overflow-hidden; + height: var(--content-height); } .see-more_content-fade { @apply absolute bottom-0 left-0 w-full h-full pointer-events-none; + background-image: linear-gradient( to bottom, transparent 70%, @@ -19,8 +21,8 @@ .see-more__checkbox { @apply absolute overflow-hidden whitespace-nowrap; - clip: rect(0 0 0 0); - clip-path: inset(100%); + + clip-path: inset(50%); height: 1px; width: 1px; } diff --git a/app/Resources/styles/stickyHeader.css b/resources/styles/_modules/stickyHeader.css similarity index 100% rename from app/Resources/styles/stickyHeader.css rename to resources/styles/_modules/stickyHeader.css diff --git a/app/Resources/styles/switch.css b/resources/styles/_modules/switch.css similarity index 64% rename from app/Resources/styles/switch.css rename to resources/styles/_modules/switch.css index e12f3bca..3065dc40 100644 --- a/app/Resources/styles/switch.css +++ b/resources/styles/_modules/switch.css @@ -11,12 +11,13 @@ } &:checked + .form-switch-slider::before { - @apply transform translate-x-8; + @apply transform translate-x-6; } &:checked + .form-switch-slider::after { - @apply transform translate-x-0 left-2; - content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath d='m10 15.172 9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z'/%3E%3C/svg%3E%0A"); + @apply transform translate-x-0 left-1.5; + + content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath d='m10 15.172 9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z'/%3E%3C/svg%3E%0A"); } &:checked + .form-switch-slider.form-switch-slider--small::before { @@ -29,7 +30,7 @@ } .form-switch-slider { - @apply relative inset-0 flex-shrink-0 w-16 h-8 transition duration-200 rounded-full cursor-pointer bg-highlight border-contrast border-3; + @apply relative inset-0 flex-shrink-0 h-8 transition duration-200 rounded-full cursor-pointer w-14 bg-highlight border-contrast border-3; &.form-switch-slider--small { @apply w-12 h-6; @@ -40,6 +41,7 @@ &::after { @apply translate-x-5; + left: 0; top: -1px; } @@ -47,16 +49,18 @@ &::before { @apply absolute z-10 w-6 h-6 transition duration-200 rounded-full shadow bg-elevated ring-1 ring-black ring-opacity-5; + content: ""; left: 1px; bottom: 1px; } &::after { - @apply absolute w-5 h-5 transition duration-150 transform translate-x-5; + @apply absolute w-4 h-4 transition duration-150 transform top-1; - content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath d='m12 10.586 4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z'/%3E%3C/svg%3E%0A"); - top: 3px; + --tw-translate-x: 1.125rem; + + content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='m12 10.586 4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z'/%3E%3C/svg%3E%0A"); left: 10px; } } diff --git a/app/Resources/styles/tailwind.css b/resources/styles/_modules/tailwind.css similarity index 100% rename from app/Resources/styles/tailwind.css rename to resources/styles/_modules/tailwind.css diff --git a/resources/styles/admin.css b/resources/styles/admin.css new file mode 100644 index 00000000..bb90fa4b --- /dev/null +++ b/resources/styles/admin.css @@ -0,0 +1,17 @@ +@import url("./_modules/tailwind.css"); +@import url("./_modules/custom.css"); +@import url("./_modules/fonts.css"); +@import url("./_modules/colors.css"); +@import url("./_modules/breadcrumb.css"); +@import url("./_modules/dropdown.css"); +@import url("./_modules/choices.css"); +@import url("./_modules/radioBtn.css"); +@import url("./_modules/colorRadioBtn.css"); +@import url("./_modules/switch.css"); +@import url("./_modules/radioToggler.css"); +@import url("./_modules/formInputTabs.css"); +@import url("./_modules/stickyHeader.css"); +@import url("./_modules/readMore.css"); +@import url("./_modules/seeMore.css"); + +@config '../../tailwind.admin.config.js'; diff --git a/resources/styles/install.css b/resources/styles/install.css new file mode 100644 index 00000000..e14d0dc0 --- /dev/null +++ b/resources/styles/install.css @@ -0,0 +1,17 @@ +@import url("./_modules/tailwind.css"); +@import url("./_modules/custom.css"); +@import url("./_modules/fonts.css"); +@import url("./_modules/colors.css"); +@import url("./_modules/breadcrumb.css"); +@import url("./_modules/dropdown.css"); +@import url("./_modules/choices.css"); +@import url("./_modules/radioBtn.css"); +@import url("./_modules/colorRadioBtn.css"); +@import url("./_modules/switch.css"); +@import url("./_modules/radioToggler.css"); +@import url("./_modules/formInputTabs.css"); +@import url("./_modules/stickyHeader.css"); +@import url("./_modules/readMore.css"); +@import url("./_modules/seeMore.css"); + +@config '../../tailwind.install.config.js'; diff --git a/resources/styles/site.css b/resources/styles/site.css new file mode 100644 index 00000000..c5e54f99 --- /dev/null +++ b/resources/styles/site.css @@ -0,0 +1,17 @@ +@import url("./_modules/tailwind.css"); +@import url("./_modules/custom.css"); +@import url("./_modules/fonts.css"); +@import url("./_modules/colors.css"); +@import url("./_modules/breadcrumb.css"); +@import url("./_modules/dropdown.css"); +@import url("./_modules/choices.css"); +@import url("./_modules/radioBtn.css"); +@import url("./_modules/colorRadioBtn.css"); +@import url("./_modules/switch.css"); +@import url("./_modules/radioToggler.css"); +@import url("./_modules/formInputTabs.css"); +@import url("./_modules/stickyHeader.css"); +@import url("./_modules/readMore.css"); +@import url("./_modules/seeMore.css"); + +@config '../../tailwind.config.js'; diff --git a/scripts/bundle-prepare.sh b/scripts/bundle-prepare.sh index 6bddd173..4d011586 100644 --- a/scripts/bundle-prepare.sh +++ b/scripts/bundle-prepare.sh @@ -1,9 +1,9 @@ -#!/bin/bash +#!/bin/sh set -e # install only production dependencies using the --no-dev option composer install --no-dev --prefer-dist --no-ansi --no-interaction --no-progress --ignore-platform-reqs # build all production static assets (css, js, images, icons, fonts, etc.) -npm run build -npm run build:static +pnpm run build +pnpm run build:static diff --git a/scripts/bundle.sh b/scripts/bundle.sh index 95e31c48..2d049601 100644 --- a/scripts/bundle.sh +++ b/scripts/bundle.sh @@ -1,30 +1,21 @@ -#!/bin/bash +#!/bin/sh set -e VERSION=$1 -COMPOSER_VERSION=$(echo "$VERSION" | perl -pe 's/(?<=[alpha|beta])\.//g') +COMPOSER_VERSION=$(echo "$VERSION" | sed -r 's/(alpha|beta|next)./\1/g') +COMPOSER_VERSION=$(echo "$COMPOSER_VERSION" | sed -r 's/next[0-9]+/dev/g') # replace composer.json version using jq -apt-get install jq -y echo "$( jq '.version = "'$COMPOSER_VERSION'"' composer.json )" > composer.json # replace CP_VERSION constant in app/config/constants sed -i "s/^defined('CP_VERSION').*/defined('CP_VERSION') || define('CP_VERSION', '$VERSION');/" ./app/Config/Constants.php -# fill CP_VERSION.env for docker build -echo "$VERSION" > ./CP_VERSION.env - -# install wget to download archives -apt-get install wget - # download GeoLite2-City archive and extract it to writable/uploads wget -c "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=$MAXMIND_LICENCE_KEY&suffix=tar.gz" -O - | tar -xz -C ./writable/uploads/ # rename extracted archives' folders mv ./writable/uploads/GeoLite2-City* ./writable/uploads/GeoLite2-City -# install rsync for file transfers -apt-get install rsync -y - # create castopod folder bundle: uses .rsync-filter (-F) file to copy only needed files -rsync -aF --progress . ./castopod +rsync -aF . ./castopod diff --git a/scripts/lint-commit-msg.sh b/scripts/lint-commit-msg.sh index c48b935b..173d5821 100755 --- a/scripts/lint-commit-msg.sh +++ b/scripts/lint-commit-msg.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh set -e # see https://github.com/conventional-changelog/commitlint/issues/885 @@ -6,14 +6,14 @@ set -e if [ "${CI_COMMIT_BEFORE_SHA}" = "0000000000000000000000000000000000000000" ]; then echo "commitlint from HEAD^" - npx commitlint --from=HEAD^ + pnpm exec commitlint --from=HEAD^ else echo "commitlint from ${CI_COMMIT_BEFORE_SHA}" br=`git branch -r --contains ${CI_COMMIT_BEFORE_SHA}` if [ ! -n $br ]; then - npx commitlint --from=HEAD^ + pnpm exec commitlint --from=HEAD^ else - npx commitlint --from="${CI_COMMIT_BEFORE_SHA}" + pnpm exec commitlint --from="${CI_COMMIT_BEFORE_SHA}" fi fi diff --git a/scripts/package.sh b/scripts/package.sh index c36c8cf5..c6269f5c 100644 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -1,10 +1,8 @@ -#!/bin/bash +#!/bin/sh set -e VERSION=$1 -apt-get install zip -y - # create zip and tar.gz packages for release upload zip -r castopod-$VERSION.zip castopod tar -zcvf castopod-$VERSION.tar.gz castopod diff --git a/spark b/spark index 225422aa..aef372bc 100644 --- a/spark +++ b/spark @@ -1,41 +1,65 @@ #!/usr/bin/env php * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. + * For the full copyright and license information, please view the LICENSE file that was distributed with this source + * code. */ /* * -------------------------------------------------------------------- - * CodeIgniter command-line tools + * CODEIGNITER COMMAND-LINE TOOLS * -------------------------------------------------------------------- * The main entry point into the CLI system and allows you to run * commands and perform maintenance on your application. - * - * Because CodeIgniter can handle CLI requests as just another web request - * this class mainly acts as a passthru to the framework itself. + */ + +/* + *--------------------------------------------------------------- + * CHECK SERVER API + *--------------------------------------------------------------- */ // Refuse to run when called from php-cgi -if (strpos(PHP_SAPI, 'cgi') === 0) { +if (str_starts_with(PHP_SAPI, 'cgi')) { exit("The cli tool is not supported when running php-cgi. It needs php-cli to function!\n\n"); } +/* + *--------------------------------------------------------------- + * CHECK PHP VERSION + *--------------------------------------------------------------- + */ + +$minPhpVersion = '8.5'; // If you update this, don't forget to update `public/index.php`. +if (version_compare(PHP_VERSION, $minPhpVersion, '<')) { + $message = sprintf( + 'Your PHP version must be %s or higher to run CodeIgniter. Current version: %s', + $minPhpVersion, + PHP_VERSION, + ); + + exit($message); +} + // We want errors to be shown when using it from the CLI. -error_reporting(-1); +error_reporting(E_ALL); ini_set('display_errors', '1'); -/** - * @var bool - * - * @deprecated No longer in use. `CodeIgniter` has `$context` property. +/* + *--------------------------------------------------------------- + * SET THE CURRENT DIRECTORY + *--------------------------------------------------------------- */ -define('SPARKED', true); // Path to the front controller define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR); @@ -52,39 +76,14 @@ chdir(FCPATH); * and fires up an environment-specific bootstrapping. */ -// Load our paths config file +// LOAD OUR PATHS CONFIG FILE // This is the line that might need to be changed, depending on your folder structure. require FCPATH . '../app/Config/Paths.php'; // ^^^ Change this line if you move your application folder -$paths = new Config\Paths(); +$paths = new Paths(); -// Location of the framework bootstrap file. -require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php'; +// LOAD THE FRAMEWORK BOOTSTRAP FILE +require $paths->systemDirectory . '/Boot.php'; -// Load environment settings from .env files into $_SERVER and $_ENV -require_once SYSTEMPATH . 'Config/DotEnv.php'; -(new CodeIgniter\Config\DotEnv(ROOTPATH))->load(); - -// Grab our CodeIgniter -$app = Config\Services::codeigniter(); -$app->initialize(); -$app->setContext('spark'); - -// Grab our Console -$console = new CodeIgniter\CLI\Console($app); - -// Show basic information before we do anything else. -if (is_int($suppress = array_search('--no-header', $_SERVER['argv'], true))) { - unset($_SERVER['argv'][$suppress]); // @codeCoverageIgnore - $suppress = true; -} - -$console->showHeader($suppress); - -// fire off the command in the main framework. -$response = $console->run(); - -if ($response->getStatusCode() >= 300) { - exit($response->getStatusCode()); -} +exit(Boot::bootSpark($paths)); diff --git a/tailwind.admin.config.js b/tailwind.admin.config.js new file mode 100644 index 00000000..f6d4fea4 --- /dev/null +++ b/tailwind.admin.config.js @@ -0,0 +1,171 @@ +import defaultTheme from "tailwindcss/defaultTheme"; +import tailwindForms from "@tailwindcss/forms"; +import tailwindTypography from "@tailwindcss/typography"; + +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./app/Views/**/*.php", + "./themes/cp_admin/**/*.php", + "./themes/cp_auth/**/*.php", + "./app/Helpers/*.php", + "./resources/**/*.ts", + ], + theme: { + extend: { + content: { + chevronRightIcon: + "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23ffffff' viewBox='0 0 24 24'%3E%3Cpath d='M13.17 12 8.22 7.05l1.42-1.41L16 12l-6.36 6.36-1.42-1.41L13.17 12Z'/%3E%3C/svg%3E%0A\")", + prohibitedIcon: + "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23ffffff' viewBox='0 0 24 24'%3E%3Cpath d='M7.0943 5.68009L18.3199 16.9057C19.3736 15.5506 20 13.8491 20 12C20 7.58172 16.4183 4 12 4C10.1509 4 8.44939 4.62644 7.0943 5.68009ZM16.9057 18.3199L5.68009 7.0943C4.62644 8.44939 4 10.1509 4 12C4 16.4183 7.58172 20 12 20C13.8491 20 15.5506 19.3736 16.9057 18.3199ZM4.92893 4.92893C6.73748 3.12038 9.23885 2 12 2C17.5228 2 22 6.47715 22 12C22 14.7611 20.8796 17.2625 19.0711 19.0711C17.2625 20.8796 14.7611 22 12 22C6.47715 22 2 17.5228 2 12C2 9.23885 3.12038 6.73748 4.92893 4.92893Z'/%3E%3C/svg%3E%0A\")", + }, + fontFamily: { + sans: ["Inter", ...defaultTheme.fontFamily.sans], + display: ["Kumbh Sans", ...defaultTheme.fontFamily.sans], + mono: ["Noto Sans Mono", ...defaultTheme.fontFamily.mono], + }, + textDecorationThickness: { + 3: "3px", + }, + textColor: { + skin: { + base: "hsl(var(--color-text-base) / )", + muted: "hsl(var(--color-text-muted) / )", + }, + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + muted: "hsl(var(--color-accent-muted) / )", + contrast: "hsl(var(--color-accent-contrast) / )", + }, + }, + backgroundColor: { + base: "hsl(var(--color-background-base) / )", + elevated: "hsl(var(--color-background-elevated) / )", + subtle: "hsl(var(--color-border-subtle) / )", + navigation: "hsl(var(--color-background-navigation) / )", + "navigation-active": + "hsl(var(--color-background-navigation-active) / )", + backdrop: "hsl(var(--color-background-backdrop) / )", + header: "hsl(var(--color-background-header) / )", + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + }, + highlight: "hsl(var(--color-background-highlight) / )", + }, + borderColor: { + subtle: "hsl(var(--color-border-subtle) / )", + contrast: "hsl(var(--color-border-contrast) / )", + navigation: "hsl(var(--color-border-navigation) / )", + "navigation-bg": + "hsl(var(--color-background-navigation) / )", + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + }, + background: { + base: "hsl(var(--color-background-base) / )", + elevated: "hsl(var(--color-background-elevated) / )", + }, + }, + ringColor: { + contrast: "hsl(var(--color-border-contrast) / )", + background: { + base: "hsl(var(--color-background-base) / )", + elevated: "hsl(var(--color-background-elevated) / )", + }, + }, + colors: { + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + }, + background: { + header: "hsl(var(--color-background-header) / )", + base: "hsl(var(--color-background-base) / )", + }, + heading: { + foreground: "hsl(var(--color-heading-foreground) / )", + background: "hsl(var(--color-heading-background) / )", + }, + pine: { + 50: "#F2FAF9", + 100: "#E7F9E4", + 200: "#bfe4e1", + 300: "#99d4cf", + 400: "#4db4aa", + 500: "#009486", + 600: "#008579", + 700: "#006D60", + 800: "#00564A", + 900: "#003D0B", + }, + }, + gridTemplateColumns: { + admin: "300px calc(100% - 300px)", + podcast: "1fr minmax(auto, 960px) 1fr", + podcastMain: "1fr minmax(200px, 300px)", + cards: "repeat(auto-fill, minmax(14rem, 1fr))", + latestEpisodes: "repeat(5, 1fr)", + colorButtons: "repeat(auto-fill, minmax(4rem, 1fr))", + platforms: "repeat(auto-fill, minmax(18rem, 1fr))", + plugins: "repeat(auto-fill, minmax(20rem, 1fr))", + radioGroup: "repeat(auto-fit, minmax(14rem, 1fr))", + }, + gridTemplateRows: { + admin: "40px 1fr", + }, + borderWidth: { + 3: "3px", + }, + ringWidth: { + 3: "3px", + }, + typography: { + DEFAULT: { + css: { + a: { + textDecoration: "underline", + fontWeight: 600, + "&:hover": { + textDecoration: "none", + }, + }, + input: { + margin: 0, + }, + }, + }, + sm: { + css: { + a: { + textDecoration: "underline", + fontWeight: 600, + "&:hover": { + textDecoration: "none", + }, + }, + }, + }, + }, + zIndex: { + 60: 60, + }, + keyframes: { + "slight-pulse": { + "0%": { transform: "scale(1)" }, + "60%": { transform: "scale(0.96)" }, + "75%": { transform: "scale(1.05)" }, + "95%": { transform: "scale(0.98)" }, + "100%": { transform: "scale(1)" }, + }, + }, + animation: { + "single-pulse": "slight-pulse 300ms linear 1", + }, + }, + }, + variants: {}, + plugins: [tailwindForms, tailwindTypography], +}; diff --git a/tailwind.config.js b/tailwind.config.js index 0da9244a..42ba73eb 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,18 +1,24 @@ -/* eslint-disable */ -const defaultTheme = require("tailwindcss/defaultTheme"); -const { nodeModuleNameResolver } = require("typescript"); +import defaultTheme from "tailwindcss/defaultTheme"; +import tailwindForms from "@tailwindcss/forms"; +import tailwindTypography from "@tailwindcss/typography"; /** @type {import('tailwindcss').Config} */ -module.exports = { +export default { content: [ "./app/Views/**/*.php", "./modules/**/Views/**/*.php", - "./themes/**/*.php", + "./themes/cp_app/**/*.php", "./app/Helpers/*.php", - "./app/Resources/**/*.ts", + "./resources/**/*.ts", ], theme: { extend: { + content: { + chevronRightIcon: + "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23ffffff' viewBox='0 0 24 24'%3E%3Cpath d='M13.17 12 8.22 7.05l1.42-1.41L16 12l-6.36 6.36-1.42-1.41L13.17 12Z'/%3E%3C/svg%3E%0A\")", + prohibitedIcon: + "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23ffffff' viewBox='0 0 24 24'%3E%3Cpath d='M7.0943 5.68009L18.3199 16.9057C19.3736 15.5506 20 13.8491 20 12C20 7.58172 16.4183 4 12 4C10.1509 4 8.44939 4.62644 7.0943 5.68009ZM16.9057 18.3199L5.68009 7.0943C4.62644 8.44939 4 10.1509 4 12C4 16.4183 7.58172 20 12 20C13.8491 20 15.5506 19.3736 16.9057 18.3199ZM4.92893 4.92893C6.73748 3.12038 9.23885 2 12 2C17.5228 2 22 6.47715 22 12C22 14.7611 20.8796 17.2625 19.0711 19.0711C17.2625 20.8796 14.7611 22 12 22C6.47715 22 2 17.5228 2 12C2 9.23885 3.12038 6.73748 4.92893 4.92893Z'/%3E%3C/svg%3E%0A\")", + }, fontFamily: { sans: ["Inter", ...defaultTheme.fontFamily.sans], display: ["Kumbh Sans", ...defaultTheme.fontFamily.sans], @@ -36,7 +42,10 @@ module.exports = { backgroundColor: { base: "hsl(var(--color-background-base) / )", elevated: "hsl(var(--color-background-elevated) / )", + subtle: "hsl(var(--color-border-subtle) / )", navigation: "hsl(var(--color-background-navigation) / )", + "navigation-active": + "hsl(var(--color-background-navigation-active) / )", backdrop: "hsl(var(--color-background-backdrop) / )", header: "hsl(var(--color-background-header) / )", accent: { @@ -68,9 +77,13 @@ module.exports = { }, }, colors: { - accent: "hsl(var(--color-accent-base) / )", + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + }, background: { header: "hsl(var(--color-background-header) / )", + base: "hsl(var(--color-background-base) / )", }, heading: { foreground: "hsl(var(--color-heading-foreground) / )", @@ -88,18 +101,6 @@ module.exports = { 800: "#00564A", 900: "#003D0B", }, - rose: { - 50: "#fcf9f8", - 100: "#fdeef2", - 200: "#fbcfe4", - 300: "#faa7cd", - 400: "#fb6ea5", - 500: "#fc437c", - 600: "#f24664", - 700: "#dd1f47", - 800: "#b21a39", - 900: "#8e162e", - }, }, gridTemplateColumns: { admin: "300px calc(100% - 300px)", @@ -108,6 +109,9 @@ module.exports = { cards: "repeat(auto-fill, minmax(14rem, 1fr))", latestEpisodes: "repeat(5, 1fr)", colorButtons: "repeat(auto-fill, minmax(4rem, 1fr))", + platforms: "repeat(auto-fill, minmax(18rem, 1fr))", + plugins: "repeat(auto-fill, minmax(20rem, 1fr))", + radioGroup: "repeat(auto-fit, minmax(14rem, 1fr))", }, gridTemplateRows: { admin: "40px 1fr", @@ -128,6 +132,9 @@ module.exports = { textDecoration: "none", }, }, + input: { + margin: 0, + }, }, }, sm: { @@ -148,9 +155,5 @@ module.exports = { }, }, variants: {}, - plugins: [ - require("@tailwindcss/forms"), - require("@tailwindcss/typography"), - require("@tailwindcss/line-clamp"), - ], + plugins: [tailwindForms, tailwindTypography], }; diff --git a/tailwind.install.config.js b/tailwind.install.config.js new file mode 100644 index 00000000..3313dcee --- /dev/null +++ b/tailwind.install.config.js @@ -0,0 +1,106 @@ +import defaultTheme from "tailwindcss/defaultTheme"; +import tailwindForms from "@tailwindcss/forms"; + +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./app/Views/**/*.php", + "./themes/cp_install/**/*.php", + "./resources/**/*.ts", + ], + theme: { + extend: { + fontFamily: { + sans: ["Inter", ...defaultTheme.fontFamily.sans], + display: ["Kumbh Sans", ...defaultTheme.fontFamily.sans], + mono: ["Noto Sans Mono", ...defaultTheme.fontFamily.mono], + }, + textDecorationThickness: { + 3: "3px", + }, + textColor: { + skin: { + base: "hsl(var(--color-text-base) / )", + muted: "hsl(var(--color-text-muted) / )", + }, + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + muted: "hsl(var(--color-accent-muted) / )", + contrast: "hsl(var(--color-accent-contrast) / )", + }, + }, + backgroundColor: { + base: "hsl(var(--color-background-base) / )", + elevated: "hsl(var(--color-background-elevated) / )", + subtle: "hsl(var(--color-border-subtle) / )", + navigation: "hsl(var(--color-background-navigation) / )", + "navigation-active": + "hsl(var(--color-background-navigation-active) / )", + backdrop: "hsl(var(--color-background-backdrop) / )", + header: "hsl(var(--color-background-header) / )", + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + }, + highlight: "hsl(var(--color-background-highlight) / )", + }, + borderColor: { + subtle: "hsl(var(--color-border-subtle) / )", + contrast: "hsl(var(--color-border-contrast) / )", + navigation: "hsl(var(--color-border-navigation) / )", + "navigation-bg": + "hsl(var(--color-background-navigation) / )", + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + }, + background: { + base: "hsl(var(--color-background-base) / )", + elevated: "hsl(var(--color-background-elevated) / )", + }, + }, + ringColor: { + contrast: "hsl(var(--color-border-contrast) / )", + background: { + base: "hsl(var(--color-background-base) / )", + elevated: "hsl(var(--color-background-elevated) / )", + }, + }, + colors: { + accent: { + base: "hsl(var(--color-accent-base) / )", + hover: "hsl(var(--color-accent-hover) / )", + }, + background: { + header: "hsl(var(--color-background-header) / )", + base: "hsl(var(--color-background-base) / )", + }, + heading: { + foreground: "hsl(var(--color-heading-foreground) / )", + background: "hsl(var(--color-heading-background) / )", + }, + pine: { + 50: "#F2FAF9", + 100: "#E7F9E4", + 200: "#bfe4e1", + 300: "#99d4cf", + 400: "#4db4aa", + 500: "#009486", + 600: "#008579", + 700: "#006D60", + 800: "#00564A", + 900: "#003D0B", + }, + }, + borderWidth: { + 3: "3px", + }, + ringWidth: { + 3: "3px", + }, + }, + }, + variants: {}, + plugins: [tailwindForms], +}; diff --git a/tests/.htaccess b/tests/.htaccess new file mode 100644 index 00000000..3462048a --- /dev/null +++ b/tests/.htaccess @@ -0,0 +1,6 @@ + + Require all denied + + + Deny from all + diff --git a/tests/README.md b/tests/README.md index c9ab19cb..d4224da6 100644 --- a/tests/README.md +++ b/tests/README.md @@ -7,14 +7,14 @@ test your application. Those details can be found in the documentation. ## Resources -- [CodeIgniter 4 User Guide on Testing](https://codeigniter4.github.io/userguide/testing/index.html) +- [CodeIgniter 4 User Guide on Testing](https://codeigniter.com/user_guide/testing/index.html) - [PHPUnit docs](https://phpunit.de/documentation.html) - [Any tutorials on Unit testing in CI4?](https://forum.codeigniter.com/showthread.php?tid=81830) ## Requirements It is recommended to use the latest version of PHPUnit. At the time of this -writing we are running version 9.x. Support for this has been built into the +writing, we are running version 9.x. Support for this has been built into the **composer.json** file that ships with CodeIgniter and can easily be installed via [Composer](https://getcomposer.org/) if you don't already have it installed globally. @@ -38,9 +38,9 @@ add `xdebug.mode=coverage` in the **php.ini** file to enable code coverage. A number of the tests use a running database. In order to set up the database edit the details for the `tests` group in **app/Config/Database.php** or -**phpunit.xml**. Make sure that you provide a database engine that is currently -running on your machine. More details on a test database setup are in the -[Testing Your Database](https://codeigniter4.github.io/userguide/testing/database.html) +**.env**. Make sure that you provide a database engine that is currently running +on your machine. More details on a test database setup are in the +[Testing Your Database](https://codeigniter.com/user_guide/testing/database.html) section of the documentation. ## Running the tests @@ -96,14 +96,12 @@ to exclude database tests, or automatically generate HTML code coverage reports. ## Test Cases Every test needs a _test case_, or class that your tests extend. CodeIgniter 4 -provides a few that you may use directly: +provides one class that you may use directly: -- `CodeIgniter\Test\CIUnitTestCase` - for basic tests with no other service - needs -- `CodeIgniter\Test\DatabaseTestTrait` - for tests that need database access +- `CodeIgniter\Test\CIUnitTestCase` -Most of the time you will want to write your own test cases to hold functions -and services common to your test suites. +Most of the time you will want to write your own test cases that extend +`CIUnitTestCase` to hold functions and services common to your test suites. ## Creating Tests @@ -118,13 +116,9 @@ how. Review the links above and always pay attention to your code coverage. ### Database Tests -Tests can include migrating, seeding, and testing against a mock or -live1 database. Be sure to modify the test case (or create your own) -to point to your seed and migrations and include any additional steps to be run -before tests in the `setUp()` method. - -1 Note: If you are using database tests that require a live database -connection you will need to rename **phpunit.xml.dist** to **phpunit.xml**, -uncomment the database configuration lines and add your connection details. -Prevent **phpunit.xml** from being tracked in your repo by adding it to -**.gitignore**. +Tests can include migrating, seeding, and testing against a mock or live +database. Be sure to modify the test case (or create your own) to point to your +seed and migrations and include any additional steps to be run before tests in +the `setUp()` method. See +[Testing Your Database](https://codeigniter.com/user_guide/testing/database.html) +for details. diff --git a/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php b/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php index 243f4682..faf98ca8 100644 --- a/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php +++ b/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Tests\Support\Database\Migrations; use CodeIgniter\Database\Migration; +use Override; class ExampleMigration extends Migration { @@ -13,27 +14,28 @@ class ExampleMigration extends Migration */ protected $DBGroup = 'tests'; + #[Override] public function up(): void { $fields = [ 'name' => [ - 'type' => 'varchar', + 'type' => 'varchar', 'constraint' => 31, ], 'uid' => [ - 'type' => 'varchar', + 'type' => 'varchar', 'constraint' => 31, ], 'class' => [ - 'type' => 'varchar', + 'type' => 'varchar', 'constraint' => 63, ], 'icon' => [ - 'type' => 'varchar', + 'type' => 'varchar', 'constraint' => 31, ], 'summary' => [ - 'type' => 'varchar', + 'type' => 'varchar', 'constraint' => 255, ], 'created_at' => [ @@ -61,6 +63,7 @@ class ExampleMigration extends Migration $this->forge->createTable('factories'); } + #[Override] public function down(): void { $this->forge->dropTable('factories'); diff --git a/tests/_support/Database/Seeds/ExampleSeeder.php b/tests/_support/Database/Seeds/ExampleSeeder.php index e519a913..52a4bed6 100644 --- a/tests/_support/Database/Seeds/ExampleSeeder.php +++ b/tests/_support/Database/Seeds/ExampleSeeder.php @@ -5,31 +5,33 @@ declare(strict_types=1); namespace Tests\Support\Database\Seeds; use CodeIgniter\Database\Seeder; +use Override; class ExampleSeeder extends Seeder { + #[Override] public function run(): void { $factories = [ [ - 'name' => 'Test Factory', - 'uid' => 'test001', - 'class' => 'Factories\Tests\NewFactory', - 'icon' => 'fas fa-puzzle-piece', + 'name' => 'Test Factory', + 'uid' => 'test001', + 'class' => 'Factories\Tests\NewFactory', + 'icon' => 'fas fa-puzzle-piece', 'summary' => 'Longer sample text for testing', ], [ - 'name' => 'Widget Factory', - 'uid' => 'widget', - 'class' => 'Factories\Tests\WidgetPlant', - 'icon' => 'fas fa-puzzle-piece', + 'name' => 'Widget Factory', + 'uid' => 'widget', + 'class' => 'Factories\Tests\WidgetPlant', + 'icon' => 'fas fa-puzzle-piece', 'summary' => 'Create widgets in your factory', ], [ - 'name' => 'Evil Factory', - 'uid' => 'evil-maker', - 'class' => 'Factories\Evil\MyFactory', - 'icon' => 'fas fa-book-dead', + 'name' => 'Evil Factory', + 'uid' => 'evil-maker', + 'class' => 'Factories\Evil\MyFactory', + 'icon' => 'fas fa-book-dead', 'summary' => 'Abandon all hope, ye who enter here', ], ]; diff --git a/tests/_support/Database/Seeds/FakeSinglePodcastApiSeeder.php b/tests/_support/Database/Seeds/FakeSinglePodcastApiSeeder.php new file mode 100644 index 00000000..3fbd17c4 --- /dev/null +++ b/tests/_support/Database/Seeds/FakeSinglePodcastApiSeeder.php @@ -0,0 +1,202 @@ + 3, + 'file_key' => 'podcasts/test/1685531765_84fb3309111ece22ca37.mp3', + 'file_size' => '2737773', + 'file_mimetype' => 'audio/mpeg', + 'file_metadata' => '{"GETID3_VERSION":"2.0.x-202207161647","filesize":2737773,"filepath":"\\/tmp","filename":"php76vXQR","filenamepath":"\\/tmp\\/php76vXQR","avdataoffset":45,"avdataend":2737773,"fileformat":"mp3","audio":{"dataformat":"mp3","channels":2,"sample_rate":48000,"bitrate":128008.9774161874,"channelmode":"stereo","bitrate_mode":"cbr","lossless":false,"encoder_options":"CBR128","compression_ratio":0.08333917800533033,"streams":[{"dataformat":"mp3","channels":2,"sample_rate":48000,"bitrate":128008.9774161874,"channelmode":"stereo","bitrate_mode":"cbr","lossless":false,"encoder_options":"CBR128","compression_ratio":0.08333917800533033}]},"tags":{"id3v2":{"encoder_settings":["Lavf58.29.100"]}},"encoding":"UTF-8","id3v2":{"header":true,"flags":{"unsynch":false,"exthead":false,"experim":false,"isfooter":false},"majorversion":4,"minorversion":0,"headerlength":45,"tag_offset_start":0,"tag_offset_end":45,"encoding":"UTF-8","comments":{"encoder_settings":["Lavf58.29.100"]},"TSSE":[{"frame_name":"TSSE","frame_flags_raw":0,"data":"Lavf58.29.100","datalength":15,"dataoffset":10,"framenamelong":"Software\\/Hardware and settings used for encoding","framenameshort":"encoder_settings","flags":{"TagAlterPreservation":false,"FileAlterPreservation":false,"ReadOnly":false,"GroupingIdentity":false,"compression":false,"Encryption":false,"Unsynchronisation":false,"DataLengthIndicator":false},"encodingid":3,"encoding":"UTF-8"}],"padding":{"start":35,"length":10,"valid":true}},"mime_type":"audio\\/mpeg","mpeg":{"audio":{"raw":{"synch":4094,"version":3,"layer":1,"protection":1,"bitrate":5,"sample_rate":1,"padding":0,"private":0,"channelmode":0,"modeextension":0,"copyright":0,"original":0,"emphasis":0},"version":"1","layer":3,"channelmode":"stereo","channels":2,"sample_rate":48000,"protection":false,"private":false,"modeextension":"","copyright":false,"original":false,"emphasis":"none","padding":false,"bitrate":128008.9774161874,"framelength":384,"bitrate_mode":"cbr","VBR_method":"Xing","xing_flags_raw":15,"xing_flags":{"frames":true,"bytes":true,"toc":true,"vbr_scale":true},"VBR_frames":7129,"VBR_bytes":2737728,"toc":[0,3,5,8,10,13,16,18,20,22,26,28,31,33,36,39,41,43,45,49,51,54,56,59,62,64,66,68,72,74,77,79,82,85,87,89,91,95,97,99,102,105,108,110,112,114,118,120,122,125,128,131,133,135,137,141,143,145,148,150,153,156,158,160,164,166,168,171,173,176,179,181,183,187,189,191,194,196,199,202,204,206,210,212,214,217,219,222,225,227,229,233,235,237,240,242,245,248,250,252],"VBR_scale":0,"VBR_bitrate":128008.9774161874}},"playtime_seconds":171.0720016831475,"tags_html":{"id3v2":{"encoder_settings":["Lavf58.29.100"]}},"bitrate":128008.9774161874,"playtime_string":"2:51"}', + 'type' => 'audio', + 'description' => null, + 'language_code' => 'pl', + 'uploaded_by' => '1', + 'updated_by' => '1', + 'uploaded_at' => '2023-05-31 11:16:05', + 'updated_at' => '2023-05-31 11:16:05', + ]; + } + + /** + * @return array{id: int, file_key: string, file_size: int, file_mimetype: string, file_metadata: string, type: string, description: null, language_code: null, uploaded_by: int, updated_by: int, uploaded_at: string, updated_at: string} + */ + public static function cover(): array + { + return [ + 'id' => 1, + 'file_key' => 'podcasts/Handle/cover.jpg', + 'file_size' => 400000, + 'file_mimetype' => 'image/jpeg', + 'file_metadata' => '{"FILE":{"FileName":"cover.jpg","FileDateTime":1654861723,"FileSize":468541,"FileType":2,"MimeType":"image\/jpeg","SectionsFound":"COMMENT"},"COMPUTED":{"html":"width=\"1400\" height=\"1400\"","Height":1400,"Width":1400,"IsColor":1},"COMMENT":["CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 90\n"],"sizes":{"tiny":{"width":40,"height":40,"mimetype":"image\/webp","extension":"webp"},"thumbnail":{"width":150,"height":150,"mimetype":"image\/webp","extension":"webp"},"medium":{"width":320,"height":320,"mimetype":"image\/webp","extension":"webp"},"large":{"width":1024,"height":1024,"mimetype":"image\/webp","extension":"webp"},"feed":{"width":1400,"height":1400},"id3":{"width":500,"height":500},"og":{"width":1200,"height":1200},"federation":{"width":400,"height":400},"webmanifest192":{"width":192,"height":192,"mimetype":"image\/png","extension":"png"},"webmanifest512":{"width":512,"height":512,"mimetype":"image\/png","extension":"png"}}}', + 'type' => 'image', + 'description' => null, + 'language_code' => null, + 'uploaded_by' => 1, + 'updated_by' => 1, + 'uploaded_at' => '2022-06-13 8:00:00', + 'updated_at' => '2022-06-13 8:00:00', + ]; + } + + /** + * @return array{id: int, file_key: string, file_size: int, file_mimetype: string, file_metadata: string, type: string, description: null, language_code: null, uploaded_by: int, updated_by: int, uploaded_at: string, updated_at: string} + */ + public static function banner(): array + { + return [ + 'id' => 2, + 'file_key' => 'podcasts/Handle/banner.jpg', + 'file_size' => 400000, + 'file_mimetype' => 'image/jpeg', + 'file_metadata' => '{"FILE":{"FileName":"banner.jpg","FileDateTime":1654861724,"FileSize":98209,"FileType":2,"MimeType":"image\/jpeg","SectionsFound":""},"COMPUTED":{"html":"width=\"1500\" height=\"500\"","Height":500,"Width":1500,"IsColor":1},"sizes":{"small":{"width":320,"height":128,"mimetype":"image\/webp","extension":"webp"},"medium":{"width":960,"height":320,"mimetype":"image\/webp","extension":"webp"},"federation":{"width":1500,"height":500}}}', + 'type' => 'image', + 'description' => null, + 'language_code' => null, + 'uploaded_by' => 1, + 'updated_by' => 1, + 'uploaded_at' => '2022-06-13 8:00:00', + 'updated_at' => '2022-06-13 8:00:00', + ]; + } + + /** + * @return array{id: int, uri: string, username: string, domain: string|false, private_key: string, public_key: string, display_name: string, summary: string, avatar_image_url: string, avatar_image_mimetype: string, cover_image_url: null, cover_image_mimetype: null, inbox_url: string, outbox_url: string, followers_url: string, followers_count: int, posts_count: int, is_blocked: int, created_at: string, updated_at: string} + */ + public static function actor(): array + { + return [ + 'id' => 1, + 'uri' => getenv('app_baseURL') . '@Handle', + 'username' => 'Handle', + 'domain' => getenv('app_baseURL'), + 'private_key' => 'private_key', + 'public_key' => 'public_key', + 'display_name' => 'Title', + 'summary' => '

    description

    ', + 'avatar_image_url' => getenv('app_baseURL') . 'media/podcasts/Handle', + 'avatar_image_mimetype' => 'image/webp', + 'cover_image_url' => null, + 'cover_image_mimetype' => null, + 'inbox_url' => getenv('app_baseURL') . '@Handle/inbox', + 'outbox_url' => getenv('app_baseURL') . '@Handle/outbox', + 'followers_url' => getenv('app_baseURL') . '@Handle/followers', + 'followers_count' => 0, + 'posts_count' => 0, + 'is_blocked' => 0, + 'created_at' => '2022-06-13 8:00:00', + 'updated_at' => '2022-06-13 8:00:00', + ]; + } + + /** + * @return array{id: int, guid: string, actor_id: int, handle: string, title: string, description_markdown: string, description_html: string, cover_id: int, banner_id: int, language_code: string, category_id: int, parental_advisory: null, owner_name: string, owner_email: string, publisher: string, type: string, copyright: string, is_blocked: int, is_completed: int, is_locked: int, imported_feed_url: null, new_feed_url: null, location_name: null, location_geo: null, location_osm: null, is_published_on_hubs: int, created_by: int, updated_by: int, created_at: string, updated_at: string} + */ + public static function podcast(): array + { + return [ + 'id' => 1, + 'guid' => '0d341200-0234-5de7-99a6-a7d02bea4ce2', + 'actor_id' => 1, + 'handle' => 'Handle', + 'title' => 'Title', + 'description_markdown' => 'description', + 'description_html' => '

    description

    ', + 'cover_id' => 1, + 'banner_id' => 2, + 'language_code' => 'en', + 'category_id' => 1, + 'parental_advisory' => null, + 'owner_name' => 'Owner', + 'owner_email' => 'Owner@gmail.com', + 'publisher' => '', + 'type' => 'episodic', + 'copyright' => '', + 'is_blocked' => 0, + 'is_completed' => 0, + 'is_locked' => 1, + 'imported_feed_url' => null, + 'new_feed_url' => null, + 'location_name' => null, + 'location_geo' => null, + 'location_osm' => null, + 'is_published_on_hubs' => 0, + 'created_by' => 1, + 'updated_by' => 1, + 'created_at' => '2022-06-13 8:00:00', + 'updated_at' => '2022-06-13 8:00:00', + ]; + } + + /** + * @return array{id:int,podcast_id:int,guid:string,title:string,slug:string,audio_id:int,description_markdown:string,description_html:string,cover_id:int,transcript_id:null,transcript_remote_url:null,chapters_id:null,chapters_remote_url:null,parental_advisory:null,number:int,season_number:null,type:string,is_blocked:false,location_name:null,location_geo:null,location_osm:null,is_published_on_hubs:false,posts_count:int,comments_count:int,is_premium:false,created_by:int,updated_by:int,published_at:null,created_at:string,updated_at:string} + */ + public static function episode(): array + { + return [ + 'id' => 1, + 'podcast_id' => 1, + 'guid' => 'http://localhost:8080/@test/episodes/muzyka-marzen', + 'title' => 'Episode title', + 'slug' => 'episode-slug', + 'audio_id' => 3, + 'description_markdown' => '123', + 'description_html' => '

    123

    ', + 'cover_id' => 1, + 'transcript_id' => null, + 'transcript_remote_url' => null, + 'chapters_id' => null, + 'chapters_remote_url' => null, + 'parental_advisory' => null, + 'number' => 1, + 'season_number' => null, + 'type' => 'full', + 'is_blocked' => false, + 'location_name' => null, + 'location_geo' => null, + 'location_osm' => null, + 'is_published_on_hubs' => false, + 'posts_count' => 0, + 'comments_count' => 0, + 'is_premium' => false, + 'created_by' => 1, + 'updated_by' => 1, + 'published_at' => null, + 'created_at' => '2023-05-31 11:16:06', + 'updated_at' => '2023-05-31 11:16:06', + ]; + } + + #[Override] + public function run(): void + { + $this->call(AppSeeder::class); + $this->call(DevSeeder::class); + $this->db->table('media') + ->insert(self::cover()); + $this->db->table('media') + ->insert(self::banner()); + $this->db->table('media') + ->insert(self::audio()); + $this->db->table('fediverse_actors') + ->insert(self::actor()); + $this->db->table('podcasts') + ->insert(self::podcast()); + $this->db->table('episodes') + ->insert(self::episode()); + } +} diff --git a/tests/_support/Libraries/ConfigReader.php b/tests/_support/Libraries/ConfigReader.php index 19a02db0..9c9080e8 100644 --- a/tests/_support/Libraries/ConfigReader.php +++ b/tests/_support/Libraries/ConfigReader.php @@ -36,8 +36,6 @@ namespace Tests\Support\Libraries; use Config\App; /** - * Class ConfigReader - * * An extension of BaseConfig that prevents the constructor from loading external values. Used to read actual local * values from a config file. */ diff --git a/tests/_support/Models/ExampleModel.php b/tests/_support/Models/ExampleModel.php index aedcdd94..55d0e18c 100644 --- a/tests/_support/Models/ExampleModel.php +++ b/tests/_support/Models/ExampleModel.php @@ -19,7 +19,7 @@ class ExampleModel extends Model protected $primaryKey = 'id'; /** - * @var string + * @var 'object' */ protected $returnType = 'object'; @@ -29,7 +29,7 @@ class ExampleModel extends Model protected $useSoftDeletes = false; /** - * @var string[] + * @var list */ protected $allowedFields = ['name', 'uid', 'class', 'icon', 'summary']; @@ -39,12 +39,12 @@ class ExampleModel extends Model protected $useTimestamps = true; /** - * @var mixed[] + * @var array|string>|string>|string */ protected $validationRules = []; /** - * @var mixed[] + * @var array> */ protected $validationMessages = []; diff --git a/tests/database/ExampleDatabaseTest.php b/tests/database/ExampleDatabaseTest.php index 0108160d..eda4a6eb 100644 --- a/tests/database/ExampleDatabaseTest.php +++ b/tests/database/ExampleDatabaseTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Tests\Database; +use CodeIgniter\Database\Seeder; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\DatabaseTestTrait; use Tests\Support\Database\Seeds\ExampleSeeder; @@ -14,7 +15,7 @@ class ExampleDatabaseTest extends CIUnitTestCase use DatabaseTestTrait; /** - * @var string + * @var class-string|list> */ protected $seed = ExampleSeeder::class; @@ -36,9 +37,15 @@ class ExampleDatabaseTest extends CIUnitTestCase $this->setPrivateProperty($model, 'tempUseSoftDeletes', true); $object = $model->first(); + + if (! is_object($object)) { + return; + } + $model->delete($object->id); // The model should no longer find it + // @phpstan-ignore-next-line $this->assertNull($model->find($object->id)); // ... but it should still be in the database diff --git a/tests/index.html b/tests/index.html new file mode 100644 index 00000000..cf672743 --- /dev/null +++ b/tests/index.html @@ -0,0 +1,9 @@ + + + + 403 Forbidden + + +

    Directory access is forbidden.

    + + diff --git a/tests/modules/Api/Rest/V1/EpisodeTest.php b/tests/modules/Api/Rest/V1/EpisodeTest.php new file mode 100644 index 00000000..990652f7 --- /dev/null +++ b/tests/modules/Api/Rest/V1/EpisodeTest.php @@ -0,0 +1,120 @@ +|list> + */ + protected $seed = FakeSinglePodcastApiSeeder::class; + + /** + * @var string + */ + protected $basePath = 'app/Database'; + + /** + * @var array + */ + private array $episode = []; + + private string $apiUrl = ''; + + #[Override] + protected function setUp(): void + { + parent::setUp(); + + $this->episode = FakeSinglePodcastApiSeeder::episode(); + + $this->episode['created_at'] = []; + $this->episode['updated_at'] = []; + $this->apiUrl = config('RestApi') + ->gateway; + } + + #[Override] + protected function tearDown(): void + { + parent::tearDown(); + + restore_error_handler(); + restore_exception_handler(); + } + + public function testList(): void + { + $result = $this->call('get', $this->apiUrl . 'episodes'); + $result->assertStatus(200); + $result->assertHeader('Content-Type', 'application/json; charset=UTF-8'); + $result->assertJSONFragment([ + 0 => $this->episode, + ]); + } + + public function testView(): void + { + $result = $this->call('get', $this->apiUrl . 'episodes/1'); + $result->assertStatus(200); + $result->assertHeader('Content-Type', 'application/json; charset=UTF-8'); + $result->assertJSONFragment($this->episode); + } + + public function testViewNotFound(): void + { + $result = $this->call('get', $this->apiUrl . 'episodes/2'); + $result->assertStatus(404); + $result->assertJSONExact( + [ + 'status' => 404, + 'error' => 404, + 'messages' => [ + 'error' => 'Episode not found', + ], + ], + ); + $result->assertHeader('Content-Type', 'application/json; charset=UTF-8'); + } + + /* + * Refreshing database to fetch empty array of episodes + */ + public function testListEmpty(): void + { + $this->regressDatabase(); + $this->migrateDatabase(); + $result = $this->call('get', $this->apiUrl . 'episodes'); + $result->assertStatus(200); + $result->assertHeader('Content-Type', 'application/json; charset=UTF-8'); + $result->assertJSONExact([]); + $this->seed($this->seed); + } +} diff --git a/tests/modules/Api/Rest/V1/PodcastTest.php b/tests/modules/Api/Rest/V1/PodcastTest.php index ba6bc019..cdf5a446 100644 --- a/tests/modules/Api/Rest/V1/PodcastTest.php +++ b/tests/modules/Api/Rest/V1/PodcastTest.php @@ -2,12 +2,14 @@ declare(strict_types=1); -namespace modules\Api\Rest\V1; +namespace Tests\Modules\Api\Rest\V1; -use App\Database\Seeds\FakeSinglePodcastApiSeeder; +use CodeIgniter\Database\Seeder; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\DatabaseTestTrait; use CodeIgniter\Test\FeatureTestTrait; +use Override; +use Tests\Support\Database\Seeds\FakeSinglePodcastApiSeeder; class PodcastTest extends CIUnitTestCase { @@ -30,9 +32,9 @@ class PodcastTest extends CIUnitTestCase protected $namespace; /** - * @var string + * @var class-string|list> */ - protected $seed = 'FakeSinglePodcastApiSeeder'; + protected $seed = FakeSinglePodcastApiSeeder::class; /** * @var string @@ -44,14 +46,13 @@ class PodcastTest extends CIUnitTestCase */ private array $podcast = []; - private string $podcastApiUrl; + private string $podcastApiUrl = ''; - /** - * @param array $data - */ - public function __construct(?string $name = null, array $data = [], $dataName = '') + #[Override] + protected function setUp(): void { - parent::__construct($name, $data, $dataName); + parent::setUp(); + $this->podcast = FakeSinglePodcastApiSeeder::podcast(); $this->podcast['created_at'] = []; $this->podcast['updated_at'] = []; @@ -59,6 +60,15 @@ class PodcastTest extends CIUnitTestCase ->gateway; } + #[Override] + protected function tearDown(): void + { + parent::tearDown(); + + restore_error_handler(); + restore_exception_handler(); + } + public function testList(): void { $result = $this->call('get', $this->podcastApiUrl . 'podcasts'); @@ -83,12 +93,12 @@ class PodcastTest extends CIUnitTestCase $result->assertStatus(404); $result->assertJSONExact( [ - 'status' => 404, - 'error' => 404, + 'status' => 404, + 'error' => 404, 'messages' => [ 'error' => 'Podcast not found', ], - ] + ], ); $result->assertHeader('Content-Type', 'application/json; charset=UTF-8'); } diff --git a/tests/modules/Plugins/ManifestTest.php b/tests/modules/Plugins/ManifestTest.php new file mode 100644 index 00000000..c5557e9b --- /dev/null +++ b/tests/modules/Plugins/ManifestTest.php @@ -0,0 +1,67 @@ +assertNotEquals($manifest->name, 'acme/hello-world'); + $this->assertNotEquals($manifest->version, '1.0.0'); + + $manifest->loadFromFile(TESTPATH . 'modules/Plugins/mocks/manifests/manifest-required.json'); + + // no errors + $this->assertEmpty($manifest->getPluginErrors('acme/hello-world')); + + // properties have been set + $this->assertEquals($manifest->name, 'acme/hello-world'); + $this->assertEquals($manifest->version, '1.0.0'); + } + + public function testLoadEmptyData(): void + { + $manifest = new Manifest('acme/hello-world'); + + $manifest->loadFromFile(TESTPATH . 'modules/Plugins/mocks/manifests/manifest-empty.json'); + + $errors = $manifest->getPluginErrors('acme/hello-world'); + + $this->assertCount(2, $errors); + + // missing required name and version + $this->assertArrayHasKey('name', $errors); + $this->assertArrayHasKey('version', $errors); + } + + public function testLoadValidData(): void + { + $manifest = new Manifest('acme/hello-world'); + + $manifest->loadFromFile(TESTPATH . 'modules/Plugins/mocks/manifests/manifest-full-valid.json'); + + // no errors + $this->assertEmpty($manifest->getPluginErrors('acme/hello-world')); + } + + public function testLoadInvalidData(): void + { + $manifest = new Manifest('acme/hello-world'); + + $manifest->loadFromFile(TESTPATH . 'modules/Plugins/mocks/manifests/manifest-full-invalid.json'); + + // errors + $this->assertNotEmpty($manifest->getPluginErrors('acme/hello-world')); + } +} diff --git a/tests/modules/Plugins/PluginsTest.php b/tests/modules/Plugins/PluginsTest.php new file mode 100644 index 00000000..4594bbbe --- /dev/null +++ b/tests/modules/Plugins/PluginsTest.php @@ -0,0 +1,190 @@ +folder = __DIR__ . '/mocks/plugins' . DIRECTORY_SEPARATOR; + + self::$plugins = new Plugins($pluginsConfig); + } + + public function testRegister(): void + { + $this->assertCount(7, self::$plugins->getAllPlugins()); + $this->assertEquals(7, self::$plugins->getInstalledCount()); + $this->assertEquals(0, self::$plugins->getActiveCount()); + } + + public function testActivateDeactivate(): void + { + $this->assertEquals(0, self::$plugins->getActiveCount()); + + $plugin = self::$plugins->getAllPlugins()[0]; + + // get first plugin and activate it + self::$plugins->activate($plugin); + + $this->assertEquals(1, self::$plugins->getActiveCount()); + $this->assertEquals(PluginStatus::ACTIVE, $plugin->getStatus()); + $this->seeInDatabase('settings', [ + 'class' => PluginsConfig::class, + 'key' => 'active', + 'value' => '1', + 'type' => 'boolean', + 'context' => 'plugin:' . $plugin->getKey(), + ]); + + // get first plugin and deactivate it + self::$plugins->deactivate($plugin); + + $this->assertEquals(0, self::$plugins->getActiveCount()); + $this->assertEquals(PluginStatus::INACTIVE, $plugin->getStatus()); + $this->seeInDatabase('settings', [ + 'class' => PluginsConfig::class, + 'key' => 'active', + 'value' => '0', + 'type' => 'boolean', + 'context' => 'plugin:' . $plugin->getKey(), + ]); + } + + public function testRunHooksActive(): void + { + $acmeAllHooksPlugin = self::$plugins->getPlugin('acme', 'all-hooks'); + + self::$plugins->activate($acmeAllHooksPlugin); + + $this->assertEquals(1, self::$plugins->getActiveCount()); + + $podcast = new Podcast(); + $this->assertEquals('', $podcast->title); + self::$plugins->runHook('rssBeforeChannel', [$podcast]); + $this->assertEquals('Podcast test', $podcast->title); + + $channel = new RssFeed(''); + $this->assertTrue(empty($channel->foo)); + self::$plugins->runHook('rssAfterChannel', [$podcast, $channel]); + $this->assertFalse(empty($channel->foo)); + + $episode = new Episode(); + $this->assertEquals('', $episode->title); + self::$plugins->runHook('rssBeforeItem', [$episode]); + $this->assertEquals('Episode test', $episode->title); + + $item = new RssFeed(''); + $this->assertTrue(empty($item->efoo)); + self::$plugins->runHook('rssAfterItem', [$episode, $item]); + $this->assertFalse(empty($item->efoo)); + + $head = new HtmlHead(); + self::$plugins->runHook('siteHead', [$head]); + + $this->assertEquals( + (string) $head, + ' foo foo ', + ); + } + + public function testRunHooksInactive(): void + { + $acmeAllHooksPlugin = self::$plugins->getPlugin('acme', 'all-hooks'); + + self::$plugins->deactivate($acmeAllHooksPlugin); + + $this->assertEquals(0, self::$plugins->getActiveCount()); + + // nothing should change when running hooks as the plugin is inactive + + $podcast = new Podcast(); + $this->assertEquals('', $podcast->title); + self::$plugins->runHook('rssBeforeChannel', [$podcast]); + $this->assertEquals('', $podcast->title); + + $channel = new RssFeed(''); + $this->assertTrue(empty($channel->foo)); + self::$plugins->runHook('rssAfterChannel', [$podcast, $channel]); + $this->assertTrue(empty($channel->foo)); + + $episode = new Episode(); + $this->assertEquals('', $episode->title); + self::$plugins->runHook('rssBeforeItem', [$episode]); + $this->assertEquals('', $episode->title); + + $item = new RssFeed(''); + $this->assertTrue(empty($item->efoo)); + self::$plugins->runHook('rssAfterItem', [$episode, $item]); + $this->assertTrue(empty($item->efoo)); + + ob_start(); + self::$plugins->runHook('siteHead', []); + $result = ob_get_contents(); + ob_end_clean(); //Discard output buffer + $this->assertEquals('', $result); + } + + public function testRunUndeclaredHook(): void + { + $acmeUndeclaredHookPlugin = self::$plugins->getPlugin('acme', 'undeclared-hook'); + + self::$plugins->activate($acmeUndeclaredHookPlugin); + + $podcast = new Podcast(); + $this->assertEquals('', $podcast->title); + self::$plugins->runHook('rssBeforeChannel', [$podcast]); + $this->assertEquals('Podcast test undeclared', $podcast->title); + + // rssAfterChannel has not been declared in plugin manifest, should not be running + $channel = new RssFeed(''); + $this->assertTrue(empty($channel->foo)); + self::$plugins->runHook('rssAfterChannel', [$podcast, $channel]); + $this->assertTrue(empty($channel->foo)); + } +} diff --git a/tests/modules/Plugins/mocks/manifests/manifest-empty.json b/tests/modules/Plugins/mocks/manifests/manifest-empty.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/tests/modules/Plugins/mocks/manifests/manifest-empty.json @@ -0,0 +1 @@ +{} diff --git a/tests/modules/Plugins/mocks/manifests/manifest-full-invalid.json b/tests/modules/Plugins/mocks/manifests/manifest-full-invalid.json new file mode 100644 index 00000000..e13a5bc0 --- /dev/null +++ b/tests/modules/Plugins/mocks/manifests/manifest-full-invalid.json @@ -0,0 +1,120 @@ +{ + "name": "acme/hello-world", + "description": true, + "version": "1.0.0", + "authors": [ + { + "name": "Acme Corporation", + "email": "acme@example.com", + "url": "https://example.com/" + } + ], + "homepage": "https://example.com/", + "license": ["MIT", "AGPLv3"], + "keywords": ["seo", "analytics"], + "hooks": ["rssAfterChannel"], + "settings": { + "general": { + "name": { + "type": "radio-group", + "label": "Name", + "options": { + "foo": { "label": "Foo", "hint": "This is a hint." }, + "bar": { "label": "Bar" } + } + }, + "email": { + "type": "email", + "label": "Email" + }, + "url": { + "type": "url", + "label": "Your website URL" + }, + "toggler": { + "type": "toggler", + "label": "Toggle this?" + }, + "number": { + "type": "number", + "label": "Number" + }, + "datetime": { + "type": "datetime", + "label": "Enter a date", + "optional": true + }, + "select": { + "type": "select", + "label": "Select something", + "options": { + "foo": { + "label": "Foo" + }, + "bar": { + "label": "Bar" + }, + "baz": { + "label": "Baz" + } + } + }, + "select-multiple": { + "type": "select-multiple", + "label": "Select multiple things", + "options": { + "foo": { + "label": "Foo" + }, + "bar": { + "label": "Bar" + }, + "baz": { + "label": "Baz" + } + } + }, + "radio-group": { + "type": "radio-group", + "label": "Radio Group", + "helper": "This is a helper.", + "options": { + "foo": { + "label": "Foo" + }, + "bar": { + "label": "Bar" + }, + "baz": { + "label": "Baz" + } + } + }, + "texting": { + "type": "textarea", + "label": "Your text", + "hint": "This is a hint." + }, + "hello": { + "type": "markdown", + "label": "Name Podcast", + "hint": "This is a hint.", + "optional": true + } + }, + "podcast": { + "name": { + "type": "text", + "label": "Name Podcast", + "hint": "This is a hint." + } + }, + "episode": { + "name": { + "type": "text", + "label": "Name Episode", + "helper": "This is a helper." + } + } + } +} diff --git a/tests/modules/Plugins/mocks/manifests/manifest-full-valid.json b/tests/modules/Plugins/mocks/manifests/manifest-full-valid.json new file mode 100644 index 00000000..f8223ba3 --- /dev/null +++ b/tests/modules/Plugins/mocks/manifests/manifest-full-valid.json @@ -0,0 +1,121 @@ +{ + "name": "acme/hello-world", + "description": "A Castopod plugin to add a hello world greeting to your RSS feed!", + "version": "1.0.0", + "authors": [ + { + "name": "Acme Corporation", + "email": "acme@example.com", + "url": "https://example.com/" + } + ], + "homepage": "https://example.com/", + "license": "MIT", + "keywords": ["seo", "analytics"], + "hooks": ["rssAfterChannel"], + "settings": { + "general": { + "name": { + "type": "radio-group", + "label": "Name", + "options": { + "foo": { "label": "Foo", "hint": "This is a hint." }, + "bar": { "label": "Bar" }, + "baz": { "label": "Baz" } + } + }, + "email": { + "type": "email", + "label": "Email" + }, + "url": { + "type": "url", + "label": "Your website URL" + }, + "toggler": { + "type": "toggler", + "label": "Toggle this?" + }, + "number": { + "type": "number", + "label": "Number" + }, + "datetime": { + "type": "datetime", + "label": "Enter a date", + "optional": true + }, + "select": { + "type": "select", + "label": "Select something", + "options": { + "foo": { + "label": "Foo" + }, + "bar": { + "label": "Bar" + }, + "baz": { + "label": "Baz" + } + } + }, + "select-multiple": { + "type": "select-multiple", + "label": "Select multiple things", + "options": { + "foo": { + "label": "Foo" + }, + "bar": { + "label": "Bar" + }, + "baz": { + "label": "Baz" + } + } + }, + "radio-group": { + "type": "radio-group", + "label": "Radio Group", + "helper": "This is a helper.", + "options": { + "foo": { + "label": "Foo" + }, + "bar": { + "label": "Bar" + }, + "baz": { + "label": "Baz" + } + } + }, + "textarea": { + "type": "textarea", + "label": "Your text", + "hint": "This is a hint." + }, + "markdown": { + "type": "markdown", + "label": "Markdown", + "hint": "This is a hint.", + "optional": true + } + }, + "podcast": { + "name": { + "type": "text", + "label": "Name Podcast", + "hint": "This is a hint." + } + }, + "episode": { + "name": { + "type": "text", + "label": "Name Episode", + "helper": "This is a helper." + } + } + } +} diff --git a/tests/modules/Plugins/mocks/manifests/manifest-required.json b/tests/modules/Plugins/mocks/manifests/manifest-required.json new file mode 100644 index 00000000..5272f045 --- /dev/null +++ b/tests/modules/Plugins/mocks/manifests/manifest-required.json @@ -0,0 +1,4 @@ +{ + "name": "acme/hello-world", + "version": "1.0.0" +} diff --git a/tests/modules/Plugins/mocks/plugins/acme/all-hooks/Plugin.php b/tests/modules/Plugins/mocks/plugins/acme/all-hooks/Plugin.php new file mode 100644 index 00000000..53c93cdc --- /dev/null +++ b/tests/modules/Plugins/mocks/plugins/acme/all-hooks/Plugin.php @@ -0,0 +1,42 @@ +title = 'Podcast test'; + } + + #[Override] + public function rssAfterChannel(Podcast $podcast, RssFeed $channel): void + { + $channel->addChild('foo', 'bar'); + } + + #[Override] + public function rssBeforeItem(Episode $episode): void + { + $episode->title = 'Episode test'; + } + + #[Override] + public function rssAfterItem(Episode $episode, RssFeed $item): void + { + $item->addChild('efoo', 'ebar'); + } + + #[Override] + public function siteHead(HtmlHead $head): void + { + $head->tag('title', 'foo'); + } +} diff --git a/tests/modules/Plugins/mocks/plugins/acme/all-hooks/manifest.json b/tests/modules/Plugins/mocks/plugins/acme/all-hooks/manifest.json new file mode 100644 index 00000000..b564821b --- /dev/null +++ b/tests/modules/Plugins/mocks/plugins/acme/all-hooks/manifest.json @@ -0,0 +1,11 @@ +{ + "name": "acme/all-hooks", + "version": "1.0.0", + "hooks": [ + "rssBeforeChannel", + "rssAfterChannel", + "rssBeforeItem", + "rssAfterItem", + "siteHead" + ] +} diff --git a/tests/modules/Plugins/mocks/plugins/acme/empty-manifest/Plugin.php b/tests/modules/Plugins/mocks/plugins/acme/empty-manifest/Plugin.php new file mode 100644 index 00000000..4aaf8422 --- /dev/null +++ b/tests/modules/Plugins/mocks/plugins/acme/empty-manifest/Plugin.php @@ -0,0 +1,9 @@ +title = 'Podcast test undeclared'; + } + + #[Override] + public function rssAfterChannel(Podcast $podcast, RssFeed $channel): void + { + $channel->addChild('foo', 'bar'); + } +} diff --git a/tests/modules/Plugins/mocks/plugins/acme/undeclared-hook/manifest.json b/tests/modules/Plugins/mocks/plugins/acme/undeclared-hook/manifest.json new file mode 100644 index 00000000..af9293ae --- /dev/null +++ b/tests/modules/Plugins/mocks/plugins/acme/undeclared-hook/manifest.json @@ -0,0 +1,5 @@ +{ + "name": "acme/all-hooks", + "version": "1.0.0", + "hooks": ["rssBeforeChannel"] +} diff --git a/tests/modules/Plugins/mocks/plugins/atlantis/empty/.gitkeep b/tests/modules/Plugins/mocks/plugins/atlantis/empty/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/modules/Plugins/mocks/plugins/atlantis/hello-broken/Plugin.php b/tests/modules/Plugins/mocks/plugins/atlantis/hello-broken/Plugin.php new file mode 100644 index 00000000..8309b442 --- /dev/null +++ b/tests/modules/Plugins/mocks/plugins/atlantis/hello-broken/Plugin.php @@ -0,0 +1,9 @@ +set('logged_in', 123); $this->assertSame(123, $session->get('logged_in')); diff --git a/tests/unit/HealthTest.php b/tests/unit/HealthTest.php index b3b5dbce..db2b46eb 100644 --- a/tests/unit/HealthTest.php +++ b/tests/unit/HealthTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); use CodeIgniter\Test\CIUnitTestCase; use Config\App; -use Config\Services; use Tests\Support\Libraries\ConfigReader; /** @@ -14,12 +13,13 @@ final class HealthTest extends CIUnitTestCase { public function testIsDefinedAppPath(): void { + /** @phpstan-ignore method.alreadyNarrowedType */ $this->assertTrue(defined('APPPATH')); } public function testBaseUrlHasBeenSet(): void { - $validation = Services::validation(); + $validation = service('validation'); $env = false; @@ -35,7 +35,7 @@ final class HealthTest extends CIUnitTestCase $config = new App(); $this->assertTrue( $validation->check($config->baseURL, 'valid_url_strict'), - 'baseURL "' . $config->baseURL . '" in .env is not valid URL' + 'baseURL "' . $config->baseURL . '" in .env is not valid URL', ); } @@ -46,7 +46,7 @@ final class HealthTest extends CIUnitTestCase // BaseURL in app/Config/App.php is a valid URL? $this->assertTrue( $validation->check($reader->baseURL, 'valid_url_strict'), - 'baseURL "' . $reader->baseURL . '" in app/Config/App.php is not valid URL' + 'baseURL "' . $reader->baseURL . '" in app/Config/App.php is not valid URL', ); } } diff --git a/themes/cp_admin/_layout.php b/themes/cp_admin/_layout.php index d23a48a4..6f78c843 100644 --- a/themes/cp_admin/_layout.php +++ b/themes/cp_admin/_layout.php @@ -8,53 +8,54 @@ $isEpisodeArea = isset($podcast) && isset($episode); - - - - - <?= $this->renderSection('title') ?> | Castopod Admin - - - - - - - ' /> - asset('styles/index.css', 'css') ?> - asset('js/admin.ts', 'js') ?> - asset('js/admin-audio-player.ts', 'js') ?> - + include('_partials/_nav_header') ?> include('_partials/_nav_aside') ?>
    -
    -
    +
    +
    -
    +
    is_premium) || ($isPodcastArea && $podcast->is_premium)): ?>
    - is_premium) ? lang('PremiumPodcasts.episode_is_premium') : lang('PremiumPodcasts.podcast_is_premium') ?> - renderSection('pageTitle') ?> + + is_premium) ? lang('PremiumPodcasts.episode_is_premium') : lang('PremiumPodcasts.podcast_is_premium') ?> + renderSection('pageTitle') ?>
    - - renderSection('pageTitle') ?> - + + renderSection('pageTitle') ?> + renderSection('headerLeft') ?>
    renderSection('headerRight') ?>
    + renderSection('subtitle') ?>
    - publication_status !== 'published'): ?> + + get('Import.current') === $podcast->handle): ?> + + + publication_status !== 'published'): ?> published_at, $podcast->id, $podcast->publication_status) ?> - + + + publication_status !== 'published'): ?> + +
    renderSection('content') ?> diff --git a/themes/cp_admin/_message_block.php b/themes/cp_admin/_message_block.php index 5f1ba623..42a34097 100644 --- a/themes/cp_admin/_message_block.php +++ b/themes/cp_admin/_message_block.php @@ -1,33 +1,33 @@ has('message')): ?> - + has('error')): ?> - + has('errors')): ?> - +
    -
    + has('warning')): ?> - + has('warnings')): ?> - +
    -
    + diff --git a/themes/cp_admin/_partials/_nav_aside.php b/themes/cp_admin/_partials/_nav_aside.php index eddfc4b0..0f2867b1 100644 --- a/themes/cp_admin/_partials/_nav_aside.php +++ b/themes/cp_admin/_partials/_nav_aside.php @@ -15,9 +15,10 @@ $isEpisodeArea = isset($podcast) && isset($episode); - \ No newline at end of file + diff --git a/themes/cp_admin/_partials/_nav_header.php b/themes/cp_admin/_partials/_nav_header.php index b3711aa9..38e25d2b 100644 --- a/themes/cp_admin/_partials/_nav_header.php +++ b/themes/cp_admin/_partials/_nav_header.php @@ -1,48 +1,57 @@ +user()); ?> +
    + class="h-full pr-1 text-xl md:hidden" aria-label="">
    - - 'html', - 'content' => esc(<< 'html', + 'content' => esc(<<{$notificationsTitle} - CODE_SAMPLE), - ], - ]; + HTML), + ], +]; - if (user()->podcasts !== []) { - foreach (user()->podcasts as $userPodcast) { - $userPodcastTitle = esc($userPodcast->title); +if ($userPodcasts !== []) { + foreach ($userPodcasts as $userPodcast) { + $userPodcastTitle = esc($userPodcast->title); - $unreadNotificationDotDisplayClass = in_array($userPodcast->actor_id, user()->actorIdsWithUnreadNotifications, true) ? '' : 'hidden'; + $unreadNotificationDotDisplayClass = in_array($userPodcast->actor_id, $actorIdsWithUnreadNotifications, true) ? '' : 'hidden'; - $items[] = [ - 'type' => 'link', - 'title' => << 'link', + 'title' => <<
    @@ -50,95 +59,100 @@
    {$userPodcastTitle}
    - CODE_SAMPLE - , - 'uri' => route_to('notification-list', $userPodcast->id), - ]; - } - } else { - $noNotificationsText = lang('Notifications.no_notifications'); - $items[] = [ - 'type' => 'html', - 'content' => esc(<< route_to('notification-list', $userPodcast->id), + ]; + } +} else { + $noNotificationsText = lang('Notifications.no_notifications'); + $items[] = [ + 'type' => 'html', + 'content' => esc(<<{$noNotificationsText} - CODE_SAMPLE), - ]; - } - ?> - - + HTML), + ]; +} +?> + + + + 'ml-auto text-2xl', + ]) ?>
    podcasts as $userPodcast) { - $checkMark = interact_as_actor_id() === $userPodcast->actor_id ? icon('check', 'ml-2 bg-accent-base text-accent-contrast rounded-full') : ''; - $userPodcastTitle = esc($userPodcast->title); +$interactButtons = ''; +foreach ($userPodcasts as $userPodcast) { + $checkMark = interact_as_actor_id() === $userPodcast->actor_id ? icon('check-fill', [ + 'class' => 'ml-2 bg-accent-base text-accent-contrast rounded-full', + ]) : ''; + $userPodcastTitle = esc($userPodcast->title); - $interactButtons .= <<
    {$userPodcastTitle}{$checkMark}
    - CODE_SAMPLE; - } + HTML; +} - $interactAsText = lang('Common.choose_interact'); - $route = route_to('interact-as-actor'); - $csrfField = csrf_field(); +$interactAsText = lang('Common.choose_interact'); +$interactAsRoute = route_to('interact-as-actor'); +$csrfField = csrf_field(); - $menuItems = [ - [ - 'type' => 'link', - 'title' => lang('Navigation.account.my-account'), - 'uri' => route_to('my-account'), - ], - [ - 'type' => 'link', - 'title' => lang('Navigation.account.change-password'), - 'uri' => route_to('change-password'), - ], - [ - 'type' => 'separator', - ], - [ - 'type' => 'link', - 'title' => lang('Navigation.account.logout'), - 'uri' => route_to('logout'), - ], - ]; +$menuItems = [ + [ + 'type' => 'link', + 'title' => lang('Navigation.account.my-account'), + 'uri' => route_to('my-account'), + ], + [ + 'type' => 'link', + 'title' => lang('Navigation.account.change-password'), + 'uri' => route_to('change-password'), + ], + [ + 'type' => 'separator', + ], + [ + 'type' => 'link', + 'title' => lang('Navigation.account.logout'), + 'uri' => route_to('logout'), + ], +]; - if (user()->podcasts !== []) { - $menuItems = array_merge([ - [ - 'type' => 'html', - 'content' => esc(<< 'html', + 'content' => esc(<< {$interactAsText} -
    + {$csrfField} {$interactButtons}
    - CODE_SAMPLE), - ], - [ - 'type' => 'separator', - ], - ], $menuItems); - } - ?> - + HTML), + ], + [ + 'type' => 'separator', + ], + ], $menuItems); +} +?> +
    \ No newline at end of file diff --git a/themes/cp_admin/_partials/_nav_menu.php b/themes/cp_admin/_partials/_nav_menu.php new file mode 100644 index 00000000..a646b81c --- /dev/null +++ b/themes/cp_admin/_partials/_nav_menu.php @@ -0,0 +1,65 @@ + diff --git a/themes/cp_admin/_partials/_user_info.php b/themes/cp_admin/_partials/_user_info.php index 61871f6a..6bb3aebb 100644 --- a/themes/cp_admin/_partials/_user_info.php +++ b/themes/cp_admin/_partials/_user_info.php @@ -1,11 +1,3 @@ -
    -
    - -
    -
    - email ?> -
    -
    @@ -14,12 +6,20 @@ username) ?>
    +
    +
    + +
    +
    + email ?> +
    +
    - roles) ?> + getGroups()) ?>
    @@ -27,6 +27,6 @@
    - permissions) ?> + getPermissions()) ?>
    diff --git a/themes/cp_admin/_sidebar.php b/themes/cp_admin/_sidebar.php index f557e5e9..76c80c5e 100644 --- a/themes/cp_admin/_sidebar.php +++ b/themes/cp_admin/_sidebar.php @@ -1,56 +1,105 @@ [ - 'icon' => 'dashboard', + 'icon' => 'dashboard-fill', // @icon("dashboard-fill") 'items' => ['admin'], ], 'podcasts' => [ - 'icon' => 'mic', - 'items' => ['podcast-list', 'podcast-create', 'podcast-import'], + 'icon' => 'mic-fill', // @icon("mic-fill") + 'items' => ['podcast-list', 'podcast-create', 'all-podcast-imports', 'podcast-imports-add'], + 'items-permissions' => [ + 'podcast-create' => 'podcasts.create', + 'all-podcast-imports' => 'podcasts.import', + 'podcast-imports-add' => 'podcasts.import', + ], + 'add-cta' => 'podcast-create', + 'count-route' => 'podcast-list', + ], + 'plugins' => [ + 'icon' => 'puzzle-fill', // @icon("puzzle-fill") + 'items' => ['plugins-installed'], + 'items-labels' => [ + 'plugins-installed' => lang('Navigation.plugins-installed') . ' (' . service('plugins')->getInstalledCount() . ')', + ], + 'items-permissions' => [ + 'plugins-installed' => 'plugins.manage', + ], + 'count' => service('plugins')->getActiveCount(), + 'count-route' => 'plugins-installed', ], 'persons' => [ - 'icon' => 'folder-user', - 'items' => ['person-list', 'person-create'], + 'icon' => 'folder-user-fill', // @icon("folder-user-fill") + 'items' => ['person-list', 'person-create'], + 'items-permissions' => [ + 'person-list' => 'persons.manage', + 'person-create' => 'persons.manage', + ], + 'add-cta' => 'person-create', + 'count' => (new PersonModel())->countAllResults(), + 'count-route' => 'person-list', ], 'fediverse' => [ - 'icon' => 'star-smile', - 'items' => ['fediverse-blocked-actors', 'fediverse-blocked-domains'], + 'icon' => 'rocket-2-fill', // @icon("rocket-2-fill") + 'items' => ['fediverse-blocked-actors', 'fediverse-blocked-domains'], + 'items-permissions' => [ + 'fediverse-blocked-actors' => 'fediverse.manage-blocks', + 'fediverse-blocked-domains' => 'fediverse.manage-blocks', + ], ], 'users' => [ - 'icon' => 'group', - 'items' => ['user-list', 'user-create'], + 'icon' => 'group-fill', // @icon("group-fill") + 'items' => ['user-list', 'user-create'], + 'items-permissions' => [ + 'user-list' => 'users.manage', + 'user-create' => 'users.manage', + ], + 'add-cta' => 'user-create', + 'count' => (new UserModel())->countAllResults(), + 'count-route' => 'user-list', ], 'pages' => [ - 'icon' => 'pages', - 'items' => ['page-list', 'page-create'], - + 'icon' => 'pages-fill', // @icon("pages-fill") + 'items' => ['page-list', 'page-create'], + 'items-permissions' => [ + 'page-list' => 'pages.manage', + 'page-create' => 'pages.manage', + ], + 'add-cta' => 'page-create', + 'count' => (new PageModel())->countAllResults(), + 'count-route' => 'page-list', ], 'settings' => [ - 'icon' => 'settings', - 'items' => ['settings-general', 'settings-theme'], + 'icon' => 'settings-3-fill', // @icon("settings-3-fill") + 'items' => ['settings-general', 'settings-theme', 'admin-about'], + 'items-permissions' => [ + 'settings-general' => 'admin.settings', + 'settings-theme' => 'admin.settings', + 'admin-about' => 'admin.settings', + ], ], -]; ?> +]; - +foreach (plugins()->getActivePlugins() as $plugin) { + $route = route_to('plugins-view', $plugin->getVendor(), $plugin->getPackage()); + $navigation['plugins']['items'][] = $route; + $navigation['plugins']['items-labels'][$route] = $plugin->getTitle(); + $navigation['plugins']['items-permissions'][$route] = 'plugins.manage'; +} + +if (auth()->user()->can('podcasts.view')) { + $navigation['podcasts']['count'] = (new PodcastModel())->countAllResults(); +} else { + $navigation['podcasts']['count'] = count(get_user_podcasts(auth()->user())); +} ?> + + + $navigation, + 'langKey' => 'Navigation', +]) ?> diff --git a/themes/cp_admin/contributor/add.php b/themes/cp_admin/contributor/create.php similarity index 57% rename from themes/cp_admin/contributor/add.php rename to themes/cp_admin/contributor/create.php index ef2325bb..123036f1 100644 --- a/themes/cp_admin/contributor/add.php +++ b/themes/cp_admin/contributor/create.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> -title)]) ?> -endSection() ?> - section('pageTitle') ?> title)]) ?> endSection() ?> @@ -14,23 +10,24 @@
    - + isRequired="true" /> - + defaultValue="" + isRequired="true" /> - + diff --git a/themes/cp_admin/contributor/edit.php b/themes/cp_admin/contributor/edit.php index edfed841..33eb9101 100644 --- a/themes/cp_admin/contributor/edit.php +++ b/themes/cp_admin/contributor/edit.php @@ -1,29 +1,25 @@ extend('_layout') ?> -section('title') ?> -username)]) ?> -endSection() ?> - section('pageTitle') ?> -username)]) ?> +username)]) ?> endSection() ?> section('content') ?> -
    + - + isRequired="true" /> - + diff --git a/themes/cp_admin/contributor/list.php b/themes/cp_admin/contributor/list.php index 6ad44137..d8546fa1 100644 --- a/themes/cp_admin/contributor/list.php +++ b/themes/cp_admin/contributor/list.php @@ -1,15 +1,12 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> section('headerRight') ?> - + + endSection() ?> @@ -19,21 +16,29 @@ [ [ 'header' => lang('Contributor.list.username'), - 'cell' => function ($contributor) { + 'cell' => function ($contributor) { return esc($contributor->username); }, ], [ 'header' => lang('Contributor.list.role'), - 'cell' => function ($contributor): string { - return lang('Contributor.roles.' . $contributor->podcast_role); + 'cell' => function ($contributor, $podcast): string { + $role = get_group_info(get_podcast_group($contributor, $podcast->id), $podcast->id)['title']; + + if ($podcast->created_by === $contributor->id) { + $role = '
    ' . icon('shield-user-fill') . '' . $role . '
    '; + } + + return $role; }, ], [ 'header' => lang('Common.actions'), - 'cell' => function ($contributor, $podcast) { - return '' . - ''; + 'cell' => function ($contributor, $podcast) { + // @icon("pencil-fill") + // @icon("delete-bin-fill") + return '' . lang('Contributor.edit') . '' . + '' . lang('Contributor.remove') . ''; }, ], ], diff --git a/themes/cp_admin/contributor/remove.php b/themes/cp_admin/contributor/remove.php new file mode 100644 index 00000000..0f4fdea6 --- /dev/null +++ b/themes/cp_admin/contributor/remove.php @@ -0,0 +1,31 @@ +extend('_layout') ?> + +section('pageTitle') ?> + $contributor->username, +]) ?> +endSection() ?> + +section('content') ?> + +
    + + + $contributor->username, + 'podcastTitle' => $podcast->title, +]) ?> + + $contributor->username, + 'podcastTitle' => $podcast->title, +]) ?> + +
    + + +
    + +
    + +endSection() ?> diff --git a/themes/cp_admin/contributor/view.php b/themes/cp_admin/contributor/view.php index 71bd3b1e..fe0fa793 100644 --- a/themes/cp_admin/contributor/view.php +++ b/themes/cp_admin/contributor/view.php @@ -1,15 +1,8 @@ extend('_layout') ?> -section('title') ?> - esc($contributor->username), - 'podcastTitle' => esc($podcast->title), -]) ?> -endSection() ?> - section('pageTitle') ?> esc($contributor->username), + 'username' => esc($contributor->username), 'podcastTitle' => esc($podcast->title), ]) ?> endSection() ?> diff --git a/themes/cp_admin/dashboard.php b/themes/cp_admin/dashboard.php index e3f6222c..6a8a3bac 100644 --- a/themes/cp_admin/dashboard.php +++ b/themes/cp_admin/dashboard.php @@ -1,36 +1,36 @@ - extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> section('content') ?> -
    - + + - + + - + + % + 'totalStorage' => $storageData['limit'], + ]) ?>">%
    - - - + 'w-8 pl-2 text-2xl rounded-r-full rounded-tl-lg text-accent-contrast bg-accent-base', + ]) ?> published_at, $episode->publication_status, 'text-sm'); ?>
    @@ -17,59 +19,61 @@ title) ?>
    - + 'link', + 'type' => 'link', 'title' => lang('Episode.go_to_page'), - 'uri' => route_to('episode', esc($episode->podcast->handle), esc($episode->slug)), + 'uri' => route_to('episode', esc($episode->podcast->handle), esc($episode->slug)), ], [ - 'type' => 'link', + 'type' => 'link', 'title' => lang('Episode.edit'), - 'uri' => route_to('episode-edit', $episode->podcast->id, $episode->id), + 'uri' => route_to('episode-edit', $episode->podcast->id, $episode->id), ], [ - 'type' => 'link', + 'type' => 'link', 'title' => lang('Episode.embed.title'), - 'uri' => route_to('embed-add', $episode->podcast->id, $episode->id), + 'uri' => route_to('embed-add', $episode->podcast->id, $episode->id), ], [ - 'type' => 'link', + 'type' => 'link', 'title' => lang('Person.persons'), - 'uri' => route_to('episode-persons-manage', $episode->podcast->id, $episode->id), + 'uri' => route_to('episode-persons-manage', $episode->podcast->id, $episode->id), ], [ - 'type' => 'link', + 'type' => 'link', 'title' => lang('VideoClip.list.title'), - 'uri' => route_to('video-clips-list', $episode->podcast->id, $episode->id), + 'uri' => route_to('video-clips-list', $episode->podcast->id, $episode->id), ], [ - 'type' => 'link', + 'type' => 'link', 'title' => lang('Soundbite.list.title'), - 'uri' => route_to('soundbites-list', $episode->podcast->id, $episode->id), + 'uri' => route_to('soundbites-list', $episode->podcast->id, $episode->id), ], [ 'type' => 'separator', ], ]; - if ($episode->published_at === null) { - $items[] = [ - 'type' => 'link', - 'title' => lang('Episode.delete'), - 'uri' => route_to('episode-delete', $episode->podcast->id, $episode->id), - 'class' => 'font-semibold text-red-600', - ]; - } else { - $label = lang('Episode.delete'); - $icon = icon('forbid', 'mr-2'); - $title = lang('Episode.messages.unpublishBeforeDeleteTip'); - $items[] = [ - 'type' => 'html', - 'content' => esc(<<published_at === null) { + $items[] = [ + 'type' => 'link', + 'title' => lang('Episode.delete'), + 'uri' => route_to('episode-delete', $episode->podcast->id, $episode->id), + 'class' => 'font-semibold text-red-600', + ]; +} else { + $label = lang('Episode.delete'); + $icon = icon('forbid-fill', [ + 'class' => 'mr-2', + ]); + $title = lang('Episode.messages.unpublishBeforeDeleteTip'); + $items[] = [ + 'type' => 'html', + 'content' => esc(<<{$icon}{$label} - CODE_SAMPLE), - ]; - } ?> - + HTML), + ]; +} ?> + \ No newline at end of file diff --git a/themes/cp_admin/episode/_sidebar.php b/themes/cp_admin/episode/_sidebar.php index fc3291e8..fd0ef42b 100644 --- a/themes/cp_admin/episode/_sidebar.php +++ b/themes/cp_admin/episode/_sidebar.php @@ -1,18 +1,50 @@ [ - 'icon' => 'dashboard', - 'items' => ['episode-view', 'episode-edit', 'episode-persons-manage', 'embed-add'], + 'icon' => 'dashboard-fill', // @icon("dashboard-fill") + 'items' => ['episode-view', 'episode-edit', 'episode-persons-manage', 'embed-add'], + 'items-permissions' => [ + 'episode-view' => 'episodes.view', + 'episode-edit' => 'episodes.edit', + 'episode-persons-manage' => 'episodes.manage-persons', + 'embed-add' => 'episodes.edit', + ], + ], + 'plugins' => [ + 'icon' => 'puzzle-fill', // @icon("puzzle-fill") + 'items' => [], + 'items-labels' => [], + 'items-permissions' => [], ], 'clips' => [ - 'icon' => 'clapperboard', - 'items' => ['video-clips-list', 'video-clips-create', 'soundbites-list', 'soundbites-create'], + 'icon' => 'clapperboard-fill', // @icon("clapperboard-fill") + 'items' => ['video-clips-list', 'video-clips-create', 'soundbites-list', 'soundbites-create'], + 'items-permissions' => [ + 'video-clips-list' => 'episodes.manage-clips', + 'video-clips-create' => 'episodes.manage-clips', + 'soundbites-list' => 'episodes.manage-clips', + 'soundbites-create' => 'episodes.manage-clips', + ], + 'count' => $episode->getClipCount(), + 'count-route' => 'video-clips-list', + 'add-cta' => 'video-clips-create', ], -]; ?> +]; - - +foreach (plugins()->getPluginsWithEpisodeSettings() as $plugin) { + $route = route_to('plugins-settings-episode', $plugin->getVendor(), $plugin->getPackage(), $podcast->id, $episode->id); + $episodeNavigation['plugins']['items'][] = $route; + $episodeNavigation['plugins']['items-labels'][$route] = $plugin->getTitle(); + $episodeNavigation['plugins']['items-permissions'][$route] = 'episodes.edit'; +} + +?> + + + 'mr-2', + ]) ?> <?= esc($podcast->title) ?>
    - + + $episodeNavigation, + 'langKey' => 'EpisodeNavigation', + 'podcastId' => $podcast->id, + 'episodeId' => $episode->id, +]) ?> diff --git a/themes/cp_admin/episode/create.php b/themes/cp_admin/episode/create.php index 4953d28b..bac39db6 100644 --- a/themes/cp_admin/episode/create.php +++ b/themes/cp_admin/episode/create.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -15,148 +11,143 @@ - + - - - -
    - - - handle) . '/episodes') . '/' ?> - - -
    +
    - -
    -
    - - - - -
    + options=" lang('Episode.form.type.full'), + 'value' => 'full', + 'description' => lang('Episode.form.type.full_description'), + ], + [ + 'label' => lang('Episode.form.type.trailer'), + 'value' => 'trailer', + 'description' => lang('Episode.form.type.trailer_description'), + ], + [ + 'label' => lang('Episode.form.type.bonus'), + 'value' => 'bonus', + 'description' => lang('Episode.form.type.bonus_description'), + ], + ])) ?>" + isRequired="true" +/> -
    - - - - - - -
    + options=" lang('Episode.form.parental_advisory.undefined'), + 'value' => 'undefined', + ], + [ + 'label' => lang('Episode.form.parental_advisory.clean'), + 'value' => 'clean', + ], + [ + 'label' => lang('Episode.form.parental_advisory.explicit'), + 'value' => 'explicit', + ], + ])) ?>" + isRequired="true" +/> + +
    - -
    - - - - - + - + + + + - - - - - - - - + label="" + hint="" /> + -
    (' . lang('Common.optional') . - ')' . - hint_tooltip(lang('Episode.form.transcript_hint'), 'ml-1') ?> + ')' ?>
    /> @@ -166,12 +157,12 @@
    - - + +
    - - + +
    @@ -182,8 +173,7 @@ (' . lang('Common.optional') . - ')' . - hint_tooltip(lang('Episode.form.chapters_hint'), 'ml-1') ?> + ')' ?>
    /> @@ -193,34 +183,29 @@
    - - + +
    - - + +
    - + - - - + - + - + + diff --git a/themes/cp_admin/episode/delete.php b/themes/cp_admin/episode/delete.php index 786ae3ff..7121d222 100644 --- a/themes/cp_admin/episode/delete.php +++ b/themes/cp_admin/episode/delete.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -13,13 +9,13 @@
    - + - +
    - - + +
    diff --git a/themes/cp_admin/episode/edit.php b/themes/cp_admin/episode/edit.php index ab7d6a44..ab57b79d 100644 --- a/themes/cp_admin/episode/edit.php +++ b/themes/cp_admin/episode/edit.php @@ -1,15 +1,11 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> section('headerRight') ?> - + endSection() ?> @@ -19,147 +15,151 @@ - + - - - + isRequired="true" /> + -
    - - - handle) . '/episodes') . '/' ?> - - -
    +
    - -
    -
    - - - - -
    + value="type ?>" + options=" lang('Episode.form.type.full'), + 'value' => 'full', + 'description' => lang('Episode.form.type.full_description'), + ], + [ + 'label' => lang('Episode.form.type.trailer'), + 'value' => 'trailer', + 'description' => lang('Episode.form.type.trailer_description'), + ], + [ + 'label' => lang('Episode.form.type.bonus'), + 'value' => 'bonus', + 'description' => lang('Episode.form.type.bonus_description'), + ], + ])) ?>" + isRequired="true" +/> -
    - - - - - - -
    + value="parental_advisory ?>" + options=" lang('Episode.form.parental_advisory.undefined'), + 'value' => 'undefined', + ], + [ + 'label' => lang('Episode.form.parental_advisory.clean'), + 'value' => 'clean', + ], + [ + 'label' => lang('Episode.form.parental_advisory.explicit'), + 'value' => 'explicit', + ], + ])) ?>" + isRequired="true" +/> -
    + - - - + - + + + + - - - - - - - - + -
    (' . lang('Common.optional') . - ')' . - hint_tooltip(lang('Episode.form.transcript_hint'), 'ml-1') ?> + ')' ?>
    transcript_remote_url ? '' : 'checked' ?> /> @@ -172,37 +172,40 @@ transcript) : ?>
    transcript->file_url, - icon('file-download', 'mr-1 text-skin-muted text-xl') . lang('Episode.form.transcript_download'), - [ - 'class' => 'flex-1 font-semibold hover:underline inline-flex items-center text-xs', - 'download' => '', - ], - ) . + $episode->transcript->file_url, + icon('file-download-fill', [ + 'class' => 'mr-1 text-skin-muted text-xl', + ]) . lang('Episode.form.transcript_download'), + [ + 'class' => 'flex-1 font-semibold hover:underline inline-flex items-center text-xs', + 'download' => '', + ], + ) . anchor( route_to( 'transcript-delete', $podcast->id, $episode->id, ), - icon('delete-bin', 'mx-auto'), + icon('delete-bin-fill', [ + 'class' => 'mx-auto', + ]), [ - 'class' => - 'p-1 text-sm bg-red-100 rounded-full text-red-700 hover:text-red-900 focus:ring-accent', + 'class' => 'p-1 text-sm bg-red-100 rounded-full text-red-700 hover:text-red-900', 'data-tooltip' => 'bottom', - 'title' => lang( + 'title' => lang( 'Episode.form.transcript_file_delete', ), ], ) ?>
    - - + +
    - - + +
    @@ -213,8 +216,7 @@ (' . lang('Common.optional') . - ')' . - hint_tooltip(lang('Episode.form.chapters_hint'), 'ml-1') ?> + ')' ?>
    chapters_remote_url ? '' : 'checked' ?> /> @@ -227,65 +229,63 @@ chapters) : ?>
    chapters->file_url, - icon('file-download', 'mr-1 text-skin-muted text-xl') . lang('Episode.form.chapters_download'), - [ - 'class' => 'flex-1 font-semibold hover:underline inline-flex items-center text-xs', - 'download' => '', - ], - ) . + $episode->chapters->file_url, + icon('file-download-fill', [ + 'class' => 'mr-1 text-skin-muted text-xl', + ]) . lang('Episode.form.chapters_download'), + [ + 'class' => 'flex-1 font-semibold hover:underline inline-flex items-center text-xs', + 'download' => '', + ], + ) . anchor( route_to( 'chapters-delete', $podcast->id, $episode->id, ), - icon('delete-bin', 'mx-auto'), + icon('delete-bin-fill', [ + 'class' => 'mx-auto', + ]), [ - 'class' => - 'text-sm p-1 bg-red-100 rounded-full text-red-700 hover:text-red-900 focus:ring-accent', + 'class' => 'text-sm p-1 bg-red-100 rounded-full text-red-700 hover:text-red-900', 'data-tooltip' => 'bottom', - 'title' => lang( + 'title' => lang( 'Episode.form.chapters_file_delete', ), ], ) ?>
    - - + +
    - - + +
    - + - - - + - + published_at === null): ?> - + + - + + diff --git a/themes/cp_admin/episode/embed.php b/themes/cp_admin/episode/embed.php index f633a04a..d64e65e4 100644 --- a/themes/cp_admin/episode/embed.php +++ b/themes/cp_admin/episode/embed.php @@ -1,8 +1,10 @@ -extend('_layout') ?> +section('title') ?> - -endSection() ?> +$embedHeight = config('Embed')->height; + +?> + +extend('_layout') ?> section('pageTitle') ?> @@ -27,13 +29,15 @@
    - embed_url}\">") ?>" /> - + embed_url}\">") ?>" /> + +
    - - + + +
    endSection() ?> diff --git a/themes/cp_admin/episode/list.php b/themes/cp_admin/episode/list.php index ff82cdfe..7c6f6e80 100644 --- a/themes/cp_admin/episode/list.php +++ b/themes/cp_admin/episode/list.php @@ -1,15 +1,12 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> section('headerRight') ?> - + + endSection() ?> @@ -22,33 +19,38 @@ ]) ?>
    $pager->getDetails()['currentPage'], - 'pageCount' => $pager->getDetails()['pageCount'], + 'pageCount' => $pager->getDetails()['pageCount'], ]) ?>

    - + 'text-xl', + ]) ?>
    - lang('Episode.list.episode'), - 'cell' => function ($episode, $podcast) { + 'cell' => function ($episode, $podcast) { $premiumBadge = ''; if ($episode->is_premium) { - $premiumBadge = ''; + $premiumBadge = icon('exchange-dollar-fill', [ + 'class' => 'absolute left-0 w-8 pl-2 text-2xl rounded-r-full rounded-tl-lg top-2 text-accent-contrast bg-accent-base', + ]); } - return '
    ' . - '
    ' . + return '
    ' . + '
    ' . '
    '; }, ], [ 'header' => lang('Episode.list.visibility'), - 'cell' => function ($episode): string { + 'cell' => function ($episode): string { return publication_pill( $episode->published_at, $episode->publication_status, + 'text-sm', ); }, ], + [ + 'header' => lang('Episode.list.downloads'), + 'cell' => function ($episode): string { + return downloads_abbr($episode->downloads_count); + }, + ], [ 'header' => lang('Episode.list.comments'), - 'cell' => function ($episode): int { + 'cell' => function ($episode): int { return $episode->comments_count; }, ], [ 'header' => lang('Episode.list.actions'), - 'cell' => function ($episode, $podcast) { + 'cell' => function ($episode, $podcast) { $items = [ [ - 'type' => 'link', + 'type' => 'link', 'title' => lang('Episode.go_to_page'), - 'uri' => route_to('episode', esc($podcast->handle), esc($episode->slug)), + 'uri' => route_to('episode', esc($podcast->handle), esc($episode->slug)), ], [ - 'type' => 'link', + 'type' => 'link', 'title' => lang('Episode.edit'), - 'uri' => route_to('episode-edit', $podcast->id, $episode->id), + 'uri' => route_to('episode-edit', $podcast->id, $episode->id), ], [ - 'type' => 'link', + 'type' => 'link', 'title' => lang('Episode.embed.title'), - 'uri' => route_to('embed-add', $podcast->id, $episode->id), + 'uri' => route_to('embed-add', $podcast->id, $episode->id), ], [ - 'type' => 'link', + 'type' => 'link', 'title' => lang('Person.persons'), - 'uri' => route_to('episode-persons-manage', $podcast->id, $episode->id), + 'uri' => route_to('episode-persons-manage', $podcast->id, $episode->id), ], [ - 'type' => 'link', + 'type' => 'link', 'title' => lang('VideoClip.list.title'), - 'uri' => route_to('video-clips-list', $episode->podcast->id, $episode->id), + 'uri' => route_to('video-clips-list', $episode->podcast->id, $episode->id), ], [ - 'type' => 'link', + 'type' => 'link', 'title' => lang('Soundbite.list.title'), - 'uri' => route_to('soundbites-list', $podcast->id, $episode->id), + 'uri' => route_to('soundbites-list', $podcast->id, $episode->id), ], [ 'type' => 'separator', @@ -131,32 +140,32 @@ data_table( ]; if ($episode->published_at === null) { $items[] = [ - 'type' => 'link', + 'type' => 'link', 'title' => lang('Episode.delete'), - 'uri' => route_to('episode-delete', $podcast->id, $episode->id), + 'uri' => route_to('episode-delete', $podcast->id, $episode->id), 'class' => 'font-semibold text-red-600', ]; } else { $label = lang('Episode.delete'); - $icon = icon('forbid'); + $icon = icon('forbid-fill'); $title = lang('Episode.messages.unpublishBeforeDeleteTip'); $items[] = [ - 'type' => 'html', - 'content' => esc(<< 'html', + 'content' => esc(<<{$icon}{$label} - CODE_SAMPLE), + HTML), ]; } - return '' . - ''; + ''; }, ], ], $episodes, 'mb-6 mt-4', - $podcast + $podcast, ) ?> links() ?> diff --git a/themes/cp_admin/episode/persons.php b/themes/cp_admin/episode/persons.php index 05e0a619..0a182c70 100644 --- a/themes/cp_admin/episode/persons.php +++ b/themes/cp_admin/episode/persons.php @@ -1,15 +1,12 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> (persons) ?>) endSection() ?> section('headerRight') ?> - + + endSection() ?> section('content') ?> @@ -17,36 +14,35 @@
    - - - - + - +
    @@ -54,11 +50,11 @@ [ [ 'header' => lang('Person.episode_form.persons'), - 'cell' => function ($person) { + 'cell' => function ($person) { return '
    ' . '' . esc($person->full_name) . '' . + '">' . esc($person->full_name) . '' . '
    ' . esc($person->full_name) . implode( @@ -85,13 +81,14 @@ ], [ 'header' => lang('Common.actions'), - 'cell' => function ($person): string { - return ''; + 'cell' => function ($person): string { + // @icon("delete-bin-fill") + return '' . lang('Person.episode_form.remove') . ''; }, ], ], $episode->persons, - 'max-w-xl mt-6' + 'max-w-xl mt-6', ) ?> endSection() ?> diff --git a/themes/cp_admin/episode/publish.php b/themes/cp_admin/episode/publish.php index 50cc5049..c06d60cc 100644 --- a/themes/cp_admin/episode/publish.php +++ b/themes/cp_admin/episode/publish.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -12,9 +8,11 @@ id, $episode->id), - icon('arrow-left', 'mr-2 text-lg') . lang('Episode.publish_form.back_to_episode_dashboard'), + icon('arrow-left-line', [ + 'class' => 'mr-2 text-lg', + ]) . lang('Episode.publish_form.back_to_episode_dashboard'), [ - 'class' => 'inline-flex items-center font-semibold mr-4 text-sm focus:ring-accent', + 'class' => 'inline-flex items-center font-semibold mr-4 text-sm', ], ) ?> @@ -37,7 +35,7 @@
    - +
    title) ?> number, - $episode->season_number, - 'text-xs font-semibold text-skin-muted !no-underline border px-1 border-gray-500', - true, - ) ?> + $episode->number, + $episode->season_number, + 'text-xs font-semibold text-skin-muted !no-underline border px-1 border-gray-500', + true, + ) ?>
    - 0 - 0 - 0 + 'mr-1 text-xl opacity-40', + ]) ?>0 + 'mr-1 text-xl opacity-40', + ]) ?>0 + 'mr-1 text-xl opacity-40', + ]) ?>0
    @@ -74,18 +78,18 @@ - +
    /> - +
    -
    @@ -93,11 +97,11 @@ - +
    - - + +
    diff --git a/themes/cp_admin/episode/publish_date_edit.php b/themes/cp_admin/episode/publish_date_edit.php index 7346e659..281f9f32 100644 --- a/themes/cp_admin/episode/publish_date_edit.php +++ b/themes/cp_admin/episode/publish_date_edit.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -12,7 +8,9 @@ id, $episode->id), - icon('arrow-left', 'mr-2 text-lg') . lang('Episode.publish_form.back_to_episode_dashboard'), + icon('arrow-left-line', [ + 'class' => 'mr-2 text-lg', + ]) . lang('Episode.publish_form.back_to_episode_dashboard'), [ 'class' => 'inline-flex items-center font-semibold mr-4 text-sm', ], @@ -22,16 +20,16 @@ - - + diff --git a/themes/cp_admin/episode/publish_edit.php b/themes/cp_admin/episode/publish_edit.php index 216d1e70..0d9ccf49 100644 --- a/themes/cp_admin/episode/publish_edit.php +++ b/themes/cp_admin/episode/publish_edit.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -12,7 +8,9 @@ id, $episode->id), - icon('arrow-left', 'mr-2 text-lg') . lang('Episode.publish_form.back_to_episode_dashboard'), + icon('arrow-left-line', [ + 'class' => 'mr-2 text-lg', + ]) . lang('Episode.publish_form.back_to_episode_dashboard'), [ 'class' => 'inline-flex items-center font-semibold mr-4 text-sm', ], @@ -39,7 +37,7 @@
    - +
    title) ?> number, - $episode->season_number, - 'text-xs font-semibold text-skin-muted !no-underline border px-1 border-gray-500', - true, - ) ?> + $episode->number, + $episode->season_number, + 'text-xs font-semibold text-skin-muted !no-underline border px-1 border-gray-500', + true, + ) ?>
    published_at) ?> @@ -67,29 +65,35 @@
    - 0 - 0 - 0 + 'mr-1 text-xl opacity-40', + ]) ?>0 + 'mr-1 text-xl opacity-40', + ]) ?>0 + 'mr-1 text-xl opacity-40', + ]) ?>0
    publication_status === 'published'): ?>
    - + 'Episode.publish_form.publication_date', + ) ?> +
    /> - +
    -
    @@ -97,11 +101,11 @@
    - +
    - - + +
    diff --git a/themes/cp_admin/episode/soundbites_list.php b/themes/cp_admin/episode/soundbites_list.php index 8a7c090f..bff27821 100644 --- a/themes/cp_admin/episode/soundbites_list.php +++ b/themes/cp_admin/episode/soundbites_list.php @@ -1,15 +1,12 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> section('headerRight') ?> - + + endSection() ?> section('content') ?> @@ -18,21 +15,21 @@ [ [ 'header' => lang('Soundbite.list.soundbite'), - 'cell' => function ($soundbite): string { + 'cell' => function ($soundbite): string { return '
    ' . esc($soundbite->title) . '' . format_duration((int) $soundbite->duration) . '
    '; }, ], [ 'header' => lang('Common.actions'), - 'cell' => function ($soundbite): string { - return '' . - 'id . '-menu" labelledby="more-dropdown-' . $soundbite->id . '" offsetY="-24" items="' . esc(json_encode([ [ - 'type' => 'link', + 'type' => 'link', 'title' => lang('Soundbite.delete'), - 'uri' => route_to('soundbites-delete', $soundbite->podcast_id, $soundbite->episode_id, $soundbite->id), + 'uri' => route_to('soundbites-delete', $soundbite->podcast_id, $soundbite->episode_id, $soundbite->id), 'class' => 'font-semibold text-red-600', ], ])) . '" />'; diff --git a/themes/cp_admin/episode/soundbites_new.php b/themes/cp_admin/episode/soundbites_new.php index f5609494..a3114777 100644 --- a/themes/cp_admin/episode/soundbites_new.php +++ b/themes/cp_admin/episode/soundbites_new.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -14,13 +10,13 @@
    - - + @@ -28,7 +24,8 @@ - + + diff --git a/themes/cp_admin/episode/unpublish.php b/themes/cp_admin/episode/unpublish.php index e0fb7aa8..5d6d0048 100644 --- a/themes/cp_admin/episode/unpublish.php +++ b/themes/cp_admin/episode/unpublish.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -13,13 +9,13 @@
    - + - +
    - - + +
    diff --git a/themes/cp_admin/episode/video_clip.php b/themes/cp_admin/episode/video_clip.php index cc536f20..40dbebc6 100644 --- a/themes/cp_admin/episode/video_clip.php +++ b/themes/cp_admin/episode/video_clip.php @@ -1,11 +1,5 @@ extend('_layout') ?> -section('title') ?> - esc($videoClip->title), -]) ?> -endSection() ?> - section('pageTitle') ?> esc($videoClip->title), diff --git a/themes/cp_admin/episode/video_clips_list.php b/themes/cp_admin/episode/video_clips_list.php index c55fcb54..b6fa6958 100644 --- a/themes/cp_admin/episode/video_clips_list.php +++ b/themes/cp_admin/episode/video_clips_list.php @@ -6,16 +6,13 @@ use CodeIgniter\I18n\Time; ?> extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> section('headerRight') ?> - + + endSection() ?> section('content') ?> @@ -23,60 +20,66 @@ use CodeIgniter\I18n\Time; [ [ 'header' => lang('VideoClip.list.status.label'), - 'cell' => function ($videoClip): string { + 'cell' => function ($videoClip): string { $pillVariantMap = [ - 'queued' => 'default', - 'pending' => 'warning', - 'running' => 'primary', + 'queued' => 'default', + 'pending' => 'warning', + 'running' => 'primary', 'canceled' => 'default', - 'failed' => 'danger', - 'passed' => 'success', + 'failed' => 'danger', + 'passed' => 'success', ]; $pillIconMap = [ - 'queued' => 'timer', - 'pending' => 'pause', - 'running' => 'loader', - 'canceled' => 'forbid', - 'failed' => 'close', - 'passed' => 'check', + 'queued' => 'timer-fill', // @icon("timer-fill") + 'pending' => 'pause-fill', // @icon("pause-fill") + 'running' => 'loader-fill', // @icon("loader-fill") + 'canceled' => 'forbid-fill', // @icon("forbid-fill") + 'failed' => 'close-fill', // @icon("close-fill") + 'passed' => 'check-fill', // @icon("check-fill") ]; $pillIconClassMap = [ - 'queued' => '', - 'pending' => '', - 'running' => 'animate-spin', + 'queued' => '', + 'pending' => '', + 'running' => 'animate-spin', 'canceled' => '', - 'failed' => '', - 'passed' => '', + 'failed' => '', + 'passed' => '', ]; - return '' . lang('VideoClip.list.status.' . $videoClip->status) . ''; + return '' . lang('VideoClip.list.status.' . $videoClip->status) . ''; }, ], [ 'header' => lang('VideoClip.list.clip'), - 'cell' => function ($videoClip): string { + 'cell' => function ($videoClip): string { $formatClass = [ 'landscape' => 'aspect-video', - 'portrait' => 'aspect-[9/16]', - 'squared' => 'aspect-square', + 'portrait' => 'aspect-[9/16]', + 'squared' => 'aspect-square', ]; - return '
    #' . $videoClip->id . ' – ' . esc($videoClip->title) . 'by ' . esc($videoClip->user->username) . '
    ' . format_duration((int) $videoClip->duration) . '
    '; + return '
    ' . icon('play-fill') . '
    #' . $videoClip->id . ' – ' . esc($videoClip->title) . 'by ' . esc($videoClip->user->username) . '
    ' . format_duration((int) $videoClip->duration) . '
    '; }, ], [ 'header' => lang('VideoClip.list.duration'), - 'cell' => function (VideoClip $videoClip): string { + 'cell' => function (VideoClip $videoClip): string { $duration = ''; if ($videoClip->job_started_at !== null) { if ($videoClip->job_ended_at !== null) { $duration = '
    ' . - '
    ' . format_duration((int) $videoClip->job_duration, true) . '
    ' . - '
    ' . relative_time($videoClip->job_ended_at) . '
    ' . + '
    ' . icon('timer-fill', [ + 'class' => 'text-sm text-gray-400', + ]) . format_duration((int) $videoClip->job_duration, true) . '
    ' . + '
    ' . icon('calendar-fill', [ + 'class' => 'text-sm text-gray-400', + ]) . relative_time($videoClip->job_ended_at) . '
    ' . '
    '; } else { - $duration = '
    ' . format_duration(($videoClip->job_started_at->difference(Time::now()))->getSeconds(), true) . '
    '; + $duration = '
    ' . icon('timer-fill', [ + 'class' => 'text-sm text-gray-400', + ]) . format_duration(($videoClip->job_started_at->difference(Time::now()))->getSeconds(), true) . '
    '; } } @@ -85,36 +88,37 @@ use CodeIgniter\I18n\Time; ], [ 'header' => lang('Common.actions'), - 'cell' => function ($videoClip): string { + 'cell' => function ($videoClip): string { $downloadButton = ''; if ($videoClip->media) { helper('misc'); $filename = 'clip-' . slugify($videoClip->title) . "-{$videoClip->start_time}-{$videoClip->end_time}"; - $downloadButton = '' . lang('VideoClip.download_clip') . ''; + // @icon("import-fill") + $downloadButton = '' . lang('VideoClip.download_clip') . ''; } return '
    ' . $downloadButton . - '' . - 'id . '-menu" labelledby="more-dropdown-' . $videoClip->id . '" offsetY="-24" items="' . esc(json_encode([ [ - 'type' => 'link', + 'type' => 'link', 'title' => lang('VideoClip.go_to_page'), - 'uri' => route_to('video-clip', $videoClip->podcast_id, $videoClip->episode_id, $videoClip->id), + 'uri' => route_to('video-clip', $videoClip->podcast_id, $videoClip->episode_id, $videoClip->id), ], [ - 'type' => 'link', + 'type' => 'link', 'title' => lang('VideoClip.retry'), - 'uri' => route_to('video-clip-retry', $videoClip->podcast_id, $videoClip->episode_id, $videoClip->id), + 'uri' => route_to('video-clip-retry', $videoClip->podcast_id, $videoClip->episode_id, $videoClip->id), ], [ 'type' => 'separator', ], [ - 'type' => 'link', + 'type' => 'link', 'title' => lang('VideoClip.delete'), - 'uri' => route_to('video-clip-delete', $videoClip->podcast_id, $videoClip->episode_id, $videoClip->id), + 'uri' => route_to('video-clip-delete', $videoClip->podcast_id, $videoClip->episode_id, $videoClip->id), 'class' => 'font-semibold text-red-600', ], ])) . '" />' . @@ -123,7 +127,7 @@ use CodeIgniter\I18n\Time; ], ], $videoClips, - 'mb-6' + 'mb-6', ) ?> links() ?> diff --git a/themes/cp_admin/episode/video_clips_new.php b/themes/cp_admin/episode/video_clips_new.php index 3f3ca52a..24f0e1e9 100644 --- a/themes/cp_admin/episode/video_clips_new.php +++ b/themes/cp_admin/episode/video_clips_new.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -17,7 +13,7 @@ <?= $episode->cover->description ?> - + @@ -27,47 +23,48 @@
    - - +
    - - + - + + isRequired="true" + hint="">
    themes as $themeName => $colors): ?> - + isRequired="true" + isSelected="" + style="--color-accent-base: ; --color-background-preview: ">
    -
    - + + +
    diff --git a/themes/cp_admin/episode/video_clips_requirements.php b/themes/cp_admin/episode/video_clips_requirements.php index f9c714aa..d1b41435 100644 --- a/themes/cp_admin/episode/video_clips_requirements.php +++ b/themes/cp_admin/episode/video_clips_requirements.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -12,14 +8,20 @@
    - + 'flex-shrink-0 text-xl text-orange-600', + ]) ?>

    $value): ?> -
    +
    'mr-1 text-white rounded-full bg-pine-500', + ]) ?>
    -
    +
    'mr-1 text-white bg-red-500 rounded-full', + ]) ?>
    diff --git a/themes/cp_admin/episode/view.php b/themes/cp_admin/episode/view.php index 6357011e..c6392a36 100644 --- a/themes/cp_admin/episode/view.php +++ b/themes/cp_admin/episode/view.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> -title) ?> -endSection() ?> - section('pageTitle') ?> title) ?> endSection() ?> @@ -12,18 +8,19 @@ published_at, $episode->publication_status, - 'text-sm ml-2 align-middle', + 'text-sm align-middle', ) ?> endSection() ?> section('headerRight') ?> publication_status === 'published'): ?> - + +> id, @@ -40,21 +37,21 @@
    - + - +
    diff --git a/themes/cp_admin/fediverse/blocked_actors.php b/themes/cp_admin/fediverse/blocked_actors.php index f8ddb350..7232de3d 100644 --- a/themes/cp_admin/fediverse/blocked_actors.php +++ b/themes/cp_admin/fediverse/blocked_actors.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -14,21 +10,25 @@
    - - + + lang('Fediverse.list.actor'), - 'cell' => function ($blockedActor) { + 'cell' => function ($blockedActor) { return esc($blockedActor->username); }, ], [ 'header' => lang('Common.actions'), - 'cell' => function ($blockedActor) { + 'cell' => function ($blockedActor) { return '
    ' . @@ -36,13 +36,13 @@ $blockedActor->id . '" />' . csrf_field() . - '' . + '' . lang('Fediverse.list.unblock') . '' . '
    '; }, ], ], $blockedActors, - 'mt-8' + 'mt-8', ) ?> diff --git a/themes/cp_admin/fediverse/blocked_domains.php b/themes/cp_admin/fediverse/blocked_domains.php index 2a80d81e..51a2049b 100644 --- a/themes/cp_admin/fediverse/blocked_domains.php +++ b/themes/cp_admin/fediverse/blocked_domains.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -14,21 +10,24 @@
    - - + + lang('Fediverse.list.actor'), - 'cell' => function ($blockedDomain) { + 'cell' => function ($blockedDomain) { return esc($blockedDomain->name); }, ], [ 'header' => lang('Common.actions'), - 'cell' => function ($blockedDomain) { + 'cell' => function ($blockedDomain) { return '
    ' . @@ -36,13 +35,13 @@ esc($blockedDomain->name) . '" />' . csrf_field() . - '' . + '' . lang('Fediverse.list.unblock') . '' . '
    '; }, ], ], $blockedDomains, - 'mt-8' + 'mt-8', ) ?> endSection() ?> diff --git a/themes/cp_admin/import/_queue_table.php b/themes/cp_admin/import/_queue_table.php new file mode 100644 index 00000000..742fd10b --- /dev/null +++ b/themes/cp_admin/import/_queue_table.php @@ -0,0 +1,146 @@ + + + lang('PodcastImport.queue.status.label'), + 'cell' => function (PodcastImportTask $importTask) { + $pillVariantMap = [ + 'queued' => 'default', + 'pending' => 'warning', + 'running' => 'primary', + 'canceled' => 'default', + 'failed' => 'danger', + 'passed' => 'success', + ]; + + $pillIconMap = [ + 'queued' => 'timer-fill', // @icon("timer-fill") + 'pending' => 'pause-fill', // @icon("pause-fill") + 'running' => 'loader-fill', // @icon("loader-fill") + 'canceled' => 'forbid-fill', // @icon("forbid-fill") + 'failed' => 'close-fill', // @icon("close-fill") + 'passed' => 'check-fill', // @icon("check-fill") + ]; + + $pillIconClassMap = [ + 'queued' => '', + 'pending' => '', + 'running' => 'animate-spin', + 'canceled' => '', + 'failed' => '', + 'passed' => '', + ]; + + $errorHint = $importTask->status === TaskStatus::Failed ? '' . esc($importTask->error) . '' : ''; + + return '
    ' . lang('PodcastImport.queue.status.' . $importTask->status->value) . '' . $errorHint . '
    '; + }, + ], + [ + 'header' => lang('PodcastImport.queue.feed'), + 'cell' => function (PodcastImportTask $importTask) { + $externalLink = icon('external-link-fill', [ + 'class' => 'ml-1', + ]); + return << + {$importTask->feed_url}{$externalLink} + @{$importTask->handle} +
    + HTML; + }, + ], + [ + 'header' => lang('PodcastImport.queue.duration'), + 'cell' => function (PodcastImportTask $importTask) { + $duration = '-'; + if ($importTask->started_at !== null) { + if ($importTask->ended_at !== null) { + $duration = '
    ' . + '
    ' . icon('timer-fill', [ + 'class' => 'text-sm text-gray-400', + ]) . format_duration((int) $importTask->getDuration(), true) . '
    ' . + '
    ' . icon('calendar-fill', [ + 'class' => 'text-sm text-gray-400', + ]) . relative_time($importTask->ended_at) . '
    ' . + '
    '; + } else { + $duration = '
    ' . icon('timer-fill', [ + 'class' => 'text-sm text-gray-400', + ]) . format_duration(($importTask->started_at->difference(Time::now()))->getSeconds(), true) . '
    '; + } + } + + return $duration; + }, + ], + [ + 'header' => lang('PodcastImport.queue.imported_episodes'), + 'cell' => function (PodcastImportTask $importTask) { + if ($importTask->episodes_count) { + $progressPercentage = (int) ($importTask->getProgress() * 100) . '%'; + $moreInfoHelper = '' . lang('PodcastImport.queue.imported_episodes_hint', [ + 'newlyImportedCount' => $importTask->episodes_newly_imported, + 'alreadyImportedCount' => $importTask->episodes_already_imported, + ]) . ''; + return << + {$progressPercentage} +

    + {$importTask->episodes_imported} out of {$importTask->episodes_count} + {$moreInfoHelper} +

    +
    + HTML; + } + + return '-'; + }, + ], + [ + 'header' => lang('Common.list.actions'), + 'cell' => function (PodcastImportTask $importTask) { + $menuItems = [ + [ + 'type' => 'separator', + ], + [ + 'type' => 'link', + 'title' => lang('PodcastImport.queue.actions.delete'), + 'uri' => route_to('podcast-imports-task-action', $importTask->id, 'delete'), + 'class' => 'font-semibold text-red-600', + ], + ]; + + if ($importTask->status === TaskStatus::Running || $importTask->status === TaskStatus::Queued) { + array_unshift($menuItems, [ + 'type' => 'link', + 'title' => lang('PodcastImport.queue.actions.cancel'), + 'uri' => route_to('podcast-imports-task-action', $importTask->id, 'cancel'), + ]); + } else { + array_unshift($menuItems, [ + 'type' => 'link', + 'title' => lang('PodcastImport.queue.actions.retry'), + 'uri' => route_to('podcast-imports-task-action', $importTask->id, 'retry'), + ], ); + } + + return '
    ' . + '' . + '' . + '
    '; + }, + ], + ], + $podcastImportsQueue, +) ?> diff --git a/themes/cp_admin/import/add_to_queue.php b/themes/cp_admin/import/add_to_queue.php new file mode 100644 index 00000000..9a706bd7 --- /dev/null +++ b/themes/cp_admin/import/add_to_queue.php @@ -0,0 +1,61 @@ +extend('_layout') ?> + +section('pageTitle') ?> + +endSection() ?> + +section('content') ?> + +
    + + + + + + + + + + + +
    + +
    + 'absolute inset-0 h-full text-xl opacity-40 left-3', + ]) ?> + +
    +
    + + + + + +
    + + + +
    + + +endSection() ?> diff --git a/themes/cp_admin/import/podcast_queue.php b/themes/cp_admin/import/podcast_queue.php new file mode 100644 index 00000000..a1aa7b6e --- /dev/null +++ b/themes/cp_admin/import/podcast_queue.php @@ -0,0 +1,16 @@ +extend('_layout') ?> + +section('pageTitle') ?> + +endSection() ?> + +section('headerRight') ?> + + +endSection() ?> + +section('content') ?> + +include('import/_queue_table'); ?> + +endSection() ?> diff --git a/themes/cp_admin/import/podcast_sync.php b/themes/cp_admin/import/podcast_sync.php new file mode 100644 index 00000000..5a57600e --- /dev/null +++ b/themes/cp_admin/import/podcast_sync.php @@ -0,0 +1,20 @@ +extend('_layout') ?> + +section('pageTitle') ?> + +endSection() ?> + +section('content') ?> +
    + + + + + +endSection() ?> diff --git a/themes/cp_admin/import/queue.php b/themes/cp_admin/import/queue.php new file mode 100644 index 00000000..06c96968 --- /dev/null +++ b/themes/cp_admin/import/queue.php @@ -0,0 +1,17 @@ +extend('_layout') ?> + +section('pageTitle') ?> + +endSection() ?> + +section('headerRight') ?> + + +endSection() ?> + + +section('content') ?> + +include('import/_queue_table'); ?> + +endSection() ?> diff --git a/themes/cp_admin/my_account/change_password.php b/themes/cp_admin/my_account/change_password.php index cbafc732..7c41193e 100644 --- a/themes/cp_admin/my_account/change_password.php +++ b/themes/cp_admin/my_account/change_password.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -13,18 +9,18 @@
    - - - + endSection() ?> diff --git a/themes/cp_admin/my_account/view.php b/themes/cp_admin/my_account/view.php index c329ea71..46ebb1c8 100644 --- a/themes/cp_admin/my_account/view.php +++ b/themes/cp_admin/my_account/view.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -12,7 +8,8 @@ section('content') ?> user(), + 'user' => auth() + ->user(), ]) ?> endSection() ?> diff --git a/themes/cp_admin/page/create.php b/themes/cp_admin/page/create.php index 50c6351b..64789484 100644 --- a/themes/cp_admin/page/create.php +++ b/themes/cp_admin/page/create.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -14,29 +10,29 @@
    - -
    - - - - - -
    + - - + diff --git a/themes/cp_admin/page/edit.php b/themes/cp_admin/page/edit.php index e1864e21..36b65daf 100644 --- a/themes/cp_admin/page/edit.php +++ b/themes/cp_admin/page/edit.php @@ -1,9 +1,5 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> endSection() ?> @@ -14,32 +10,33 @@
    - -
    - - - - - -
    + - - + diff --git a/themes/cp_admin/page/list.php b/themes/cp_admin/page/list.php index 3d5d1ec0..b03b5e20 100644 --- a/themes/cp_admin/page/list.php +++ b/themes/cp_admin/page/list.php @@ -1,15 +1,12 @@ extend('_layout') ?> -section('title') ?> - -endSection() ?> - section('pageTitle') ?> () endSection() ?> section('headerRight') ?> - + + endSection() ?> @@ -19,7 +16,7 @@ [ [ 'header' => lang('Page.page'), - 'cell' => function ($page) { + 'cell' => function ($page) { return '
    ' . esc($page->title) . '/' . @@ -29,10 +26,10 @@ ], [ 'header' => lang('Common.actions'), - 'cell' => function ($page) { - return '' . - '' . - ''; + 'cell' => function ($page) { + return '' . lang('Page.go_to_page') . '' . + '' . lang('Page.edit') . '' . + '' . lang('Page.delete') . ''; }, ], ], diff --git a/themes/cp_admin/page/view.php b/themes/cp_admin/page/view.php index abc918ae..74f001f1 100644 --- a/themes/cp_admin/page/view.php +++ b/themes/cp_admin/page/view.php @@ -1,15 +1,12 @@ extend('_layout') ?> -section('title') ?> -title) ?> -endSection() ?> - section('pageTitle') ?> title) ?> endSection() ?> section('headerRight') ?> - + + endSection() ?> section('content') ?> diff --git a/themes/cp_admin/person/_card.php b/themes/cp_admin/person/_card.php index 20408bb5..b4506c8c 100644 --- a/themes/cp_admin/person/_card.php +++ b/themes/cp_admin/person/_card.php @@ -2,31 +2,31 @@
    - <?= esc($person->full_name) ?> + <?= esc($person->full_name) ?>

    full_name) ?>

    - -